Statistics
| Branch: | Tag: | Revision:

root / qa / qa_instance.py @ b0b7bf8f

History | View | Annotate | Download (30.9 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 _DestroyInstanceDisks(instance):
170
  """Remove all the backend disks 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
  # FIXME: destruction/removal should be part of the disk class
180
  if info["storage-type"] == constants.ST_LVM_VG:
181
    vols = info["volumes"]
182
    for node in info["nodes"]:
183
      AssertCommand(["lvremove", "-f"] + vols, node=node)
184
  elif info["storage-type"] == constants.ST_FILE:
185
    # FIXME: file storage dir not configurable in qa
186
    # Note that this works for both file and sharedfile, and this is intended.
187
    filestorage = pathutils.DEFAULT_FILE_STORAGE_DIR
188
    idir = os.path.join(filestorage, instance.name)
189
    for node in info["nodes"]:
190
      AssertCommand(["rm", "-rf", idir], node=node)
191
  elif info["storage-type"] == constants.ST_DISKLESS:
192
    pass
193

    
194

    
195
def _GetInstanceField(instance, field):
196
  """Get the value of a field of an instance.
197

198
  @type instance: string
199
  @param instance: Instance name
200
  @type field: string
201
  @param field: Name of the field
202
  @rtype: string
203

204
  """
205
  master = qa_config.GetMasterNode()
206
  infocmd = utils.ShellQuoteArgs(["gnt-instance", "list", "--no-headers",
207
                                  "--units", "m", "-o", field, instance])
208
  return qa_utils.GetCommandOutput(master.primary, infocmd).strip()
209

    
210

    
211
def _GetBoolInstanceField(instance, field):
212
  """Get the Boolean value of a field of an instance.
213

214
  @type instance: string
215
  @param instance: Instance name
216
  @type field: string
217
  @param field: Name of the field
218
  @rtype: bool
219

220
  """
221
  info_out = _GetInstanceField(instance, field)
222
  if info_out == "Y":
223
    return True
224
  elif info_out == "N":
225
    return False
226
  else:
227
    raise qa_error.Error("Field %s of instance %s has a non-Boolean value:"
228
                         " %s" % (field, instance, info_out))
229

    
230

    
231
def _GetNumInstanceField(instance, field):
232
  """Get a numeric value of a field of an instance.
233

234
  @type instance: string
235
  @param instance: Instance name
236
  @type field: string
237
  @param field: Name of the field
238
  @rtype: int or float
239

240
  """
241
  info_out = _GetInstanceField(instance, field)
242
  try:
243
    ret = int(info_out)
244
  except ValueError:
245
    try:
246
      ret = float(info_out)
247
    except ValueError:
248
      raise qa_error.Error("Field %s of instance %s has a non-numeric value:"
249
                           " %s" % (field, instance, info_out))
250
  return ret
251

    
252

    
253
def GetInstanceSpec(instance, spec):
254
  """Return the current spec for the given parameter.
255

256
  @type instance: string
257
  @param instance: Instance name
258
  @type spec: string
259
  @param spec: one of the supported parameters: "mem-size", "cpu-count",
260
      "disk-count", "disk-size", "nic-count"
261
  @rtype: tuple
262
  @return: (minspec, maxspec); minspec and maxspec can be different only for
263
      memory and disk size
264

265
  """
266
  specmap = {
267
    "mem-size": ["be/minmem", "be/maxmem"],
268
    "cpu-count": ["vcpus"],
269
    "disk-count": ["disk.count"],
270
    "disk-size": ["disk.size/ "],
271
    "nic-count": ["nic.count"],
272
    }
273
  # For disks, first we need the number of disks
274
  if spec == "disk-size":
275
    (numdisk, _) = GetInstanceSpec(instance, "disk-count")
276
    fields = ["disk.size/%s" % k for k in range(0, numdisk)]
277
  else:
278
    assert spec in specmap, "%s not in %s" % (spec, specmap)
279
    fields = specmap[spec]
280
  values = [_GetNumInstanceField(instance, f) for f in fields]
281
  return (min(values), max(values))
282

    
283

    
284
def IsFailoverSupported(instance):
285
  return instance.disk_template in constants.DTS_MIRRORED
286

    
287

    
288
def IsMigrationSupported(instance):
289
  return instance.disk_template in constants.DTS_MIRRORED
290

    
291

    
292
def IsDiskReplacingSupported(instance):
293
  return instance.disk_template == constants.DT_DRBD8
294

    
295

    
296
def TestInstanceAddWithPlainDisk(nodes, fail=False):
297
  """gnt-instance add -t plain"""
298
  assert len(nodes) == 1
299
  instance = _DiskTest(nodes[0].primary, constants.DT_PLAIN, fail=fail)
300
  if not fail:
301
    qa_utils.RunInstanceCheck(instance, True)
302
  return instance
303

    
304

    
305
@InstanceCheck(None, INST_UP, RETURN_VALUE)
306
def TestInstanceAddWithDrbdDisk(nodes):
307
  """gnt-instance add -t drbd"""
308
  assert len(nodes) == 2
309
  return _DiskTest(":".join(map(operator.attrgetter("primary"), nodes)),
310
                   constants.DT_DRBD8)
311

    
312

    
313
@InstanceCheck(None, INST_UP, RETURN_VALUE)
314
def TestInstanceAddFile(nodes):
315
  """gnt-instance add -t file"""
316
  assert len(nodes) == 1
317
  return _DiskTest(nodes[0].primary, constants.DT_FILE)
318

    
319

    
320
@InstanceCheck(None, INST_UP, RETURN_VALUE)
321
def TestInstanceAddDiskless(nodes):
322
  """gnt-instance add -t diskless"""
323
  assert len(nodes) == 1
324
  return _DiskTest(nodes[0].primary, constants.DT_DISKLESS)
325

    
326

    
327
@InstanceCheck(None, INST_DOWN, FIRST_ARG)
328
def TestInstanceRemove(instance):
329
  """gnt-instance remove"""
330
  AssertCommand(["gnt-instance", "remove", "-f", instance.name])
331

    
332

    
333
@InstanceCheck(INST_DOWN, INST_UP, FIRST_ARG)
334
def TestInstanceStartup(instance):
335
  """gnt-instance startup"""
336
  AssertCommand(["gnt-instance", "startup", instance.name])
337

    
338

    
339
@InstanceCheck(INST_UP, INST_DOWN, FIRST_ARG)
340
def TestInstanceShutdown(instance):
341
  """gnt-instance shutdown"""
342
  AssertCommand(["gnt-instance", "shutdown", instance.name])
343

    
344

    
345
@InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
346
def TestInstanceReboot(instance):
347
  """gnt-instance reboot"""
348
  options = qa_config.get("options", {})
349
  reboot_types = options.get("reboot-types", constants.REBOOT_TYPES)
350
  name = instance.name
351
  for rtype in reboot_types:
352
    AssertCommand(["gnt-instance", "reboot", "--type=%s" % rtype, name])
353

    
354
  AssertCommand(["gnt-instance", "shutdown", name])
355
  qa_utils.RunInstanceCheck(instance, False)
356
  AssertCommand(["gnt-instance", "reboot", name])
357

    
358
  master = qa_config.GetMasterNode()
359
  cmd = ["gnt-instance", "list", "--no-headers", "-o", "status", name]
360
  result_output = qa_utils.GetCommandOutput(master.primary,
361
                                            utils.ShellQuoteArgs(cmd))
362
  AssertEqual(result_output.strip(), constants.INSTST_RUNNING)
363

    
364

    
365
@InstanceCheck(INST_DOWN, INST_DOWN, FIRST_ARG)
366
def TestInstanceReinstall(instance):
367
  """gnt-instance reinstall"""
368
  if instance.disk_template == constants.DT_DISKLESS:
369
    print qa_utils.FormatInfo("Test not supported for diskless instances")
370
    return
371

    
372
  AssertCommand(["gnt-instance", "reinstall", "-f", instance.name])
373

    
374
  # Test with non-existant OS definition
375
  AssertCommand(["gnt-instance", "reinstall", "-f",
376
                 "--os-type=NonExistantOsForQa",
377
                 instance.name],
378
                fail=True)
379

    
380

    
381
def _ReadSsconfInstanceList():
382
  """Reads ssconf_instance_list from the master node.
383

384
  """
385
  master = qa_config.GetMasterNode()
386

    
387
  ssconf_path = utils.PathJoin(pathutils.DATA_DIR,
388
                               "ssconf_%s" % constants.SS_INSTANCE_LIST)
389

    
390
  cmd = ["cat", qa_utils.MakeNodePath(master, ssconf_path)]
391

    
392
  return qa_utils.GetCommandOutput(master.primary,
393
                                   utils.ShellQuoteArgs(cmd)).splitlines()
394

    
395

    
396
def _CheckSsconfInstanceList(instance):
397
  """Checks if a certain instance is in the ssconf instance list.
398

399
  @type instance: string
400
  @param instance: Instance name
401

402
  """
403
  AssertIn(qa_utils.ResolveInstanceName(instance),
404
           _ReadSsconfInstanceList())
405

    
406

    
407
@InstanceCheck(INST_DOWN, INST_DOWN, FIRST_ARG)
408
def TestInstanceRenameAndBack(rename_source, rename_target):
409
  """gnt-instance rename
410

411
  This must leave the instance with the original name, not the target
412
  name.
413

414
  """
415
  _CheckSsconfInstanceList(rename_source)
416

    
417
  # first do a rename to a different actual name, expecting it to fail
418
  qa_utils.AddToEtcHosts(["meeeeh-not-exists", rename_target])
419
  try:
420
    AssertCommand(["gnt-instance", "rename", rename_source, rename_target],
421
                  fail=True)
422
    _CheckSsconfInstanceList(rename_source)
423
  finally:
424
    qa_utils.RemoveFromEtcHosts(["meeeeh-not-exists", rename_target])
425

    
426
  info = _GetInstanceInfo(rename_source)
427

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

    
443
  # and now rename instance to rename_target...
444
  AssertCommand(["gnt-instance", "rename", rename_source, rename_target])
445
  _CheckSsconfInstanceList(rename_target)
446
  qa_utils.RunInstanceCheck(rename_source, False)
447
  qa_utils.RunInstanceCheck(rename_target, False)
448

    
449
  # NOTE: tags might not be the exactly as the instance name, due to
450
  # charset restrictions; hence the test might be flaky
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=True)
455
      AssertCommand(tags_cmd + rename_target, node=node, fail=False)
456

    
457
  # and back
458
  AssertCommand(["gnt-instance", "rename", rename_target, rename_source])
459
  _CheckSsconfInstanceList(rename_source)
460
  qa_utils.RunInstanceCheck(rename_target, False)
461

    
462
  if (rename_source != rename_target and
463
      info["storage-type"] == constants.ST_LVM_VG):
464
    for node in info["nodes"]:
465
      AssertCommand(tags_cmd + rename_source, node=node, fail=False)
466
      AssertCommand(tags_cmd + rename_target, node=node, fail=True)
467

    
468

    
469
@InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
470
def TestInstanceFailover(instance):
471
  """gnt-instance failover"""
472
  if not IsFailoverSupported(instance):
473
    print qa_utils.FormatInfo("Instance doesn't support failover, skipping"
474
                              " test")
475
    return
476

    
477
  cmd = ["gnt-instance", "failover", "--force", instance.name]
478

    
479
  # failover ...
480
  AssertCommand(cmd)
481
  qa_utils.RunInstanceCheck(instance, True)
482

    
483
  # ... and back
484
  AssertCommand(cmd)
485

    
486

    
487
@InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
488
def TestInstanceMigrate(instance, toggle_always_failover=True):
489
  """gnt-instance migrate"""
490
  if not IsMigrationSupported(instance):
491
    print qa_utils.FormatInfo("Instance doesn't support migration, skipping"
492
                              " test")
493
    return
494

    
495
  cmd = ["gnt-instance", "migrate", "--force", instance.name]
496
  af_par = constants.BE_ALWAYS_FAILOVER
497
  af_field = "be/" + constants.BE_ALWAYS_FAILOVER
498
  af_init_val = _GetBoolInstanceField(instance.name, af_field)
499

    
500
  # migrate ...
501
  AssertCommand(cmd)
502
  # TODO: Verify the choice between failover and migration
503
  qa_utils.RunInstanceCheck(instance, True)
504

    
505
  # ... and back (possibly with always_failover toggled)
506
  if toggle_always_failover:
507
    AssertCommand(["gnt-instance", "modify", "-B",
508
                   ("%s=%s" % (af_par, not af_init_val)),
509
                   instance.name])
510
  AssertCommand(cmd)
511
  # TODO: Verify the choice between failover and migration
512
  qa_utils.RunInstanceCheck(instance, True)
513
  if toggle_always_failover:
514
    AssertCommand(["gnt-instance", "modify", "-B",
515
                   ("%s=%s" % (af_par, af_init_val)), instance.name])
516

    
517
  # TODO: Split into multiple tests
518
  AssertCommand(["gnt-instance", "shutdown", instance.name])
519
  qa_utils.RunInstanceCheck(instance, False)
520
  AssertCommand(cmd, fail=True)
521
  AssertCommand(["gnt-instance", "migrate", "--force", "--allow-failover",
522
                 instance.name])
523
  AssertCommand(["gnt-instance", "start", instance.name])
524
  AssertCommand(cmd)
525
  # @InstanceCheck enforces the check that the instance is running
526
  qa_utils.RunInstanceCheck(instance, True)
527

    
528
  AssertCommand(["gnt-instance", "modify", "-B",
529
                 ("%s=%s" %
530
                  (constants.BE_ALWAYS_FAILOVER, constants.VALUE_TRUE)),
531
                 instance.name])
532

    
533
  AssertCommand(cmd)
534
  qa_utils.RunInstanceCheck(instance, True)
535
  # TODO: Verify that a failover has been done instead of a migration
536

    
537
  # TODO: Verify whether the default value is restored here (not hardcoded)
538
  AssertCommand(["gnt-instance", "modify", "-B",
539
                 ("%s=%s" %
540
                  (constants.BE_ALWAYS_FAILOVER, constants.VALUE_FALSE)),
541
                 instance.name])
542

    
543
  AssertCommand(cmd)
544
  qa_utils.RunInstanceCheck(instance, True)
545

    
546

    
547
def TestInstanceInfo(instance):
548
  """gnt-instance info"""
549
  AssertCommand(["gnt-instance", "info", instance.name])
550

    
551

    
552
@InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
553
def TestInstanceModify(instance):
554
  """gnt-instance modify"""
555
  default_hv = qa_config.GetDefaultHypervisor()
556

    
557
  # Assume /sbin/init exists on all systems
558
  test_kernel = "/sbin/init"
559
  test_initrd = test_kernel
560

    
561
  orig_maxmem = qa_config.get(constants.BE_MAXMEM)
562
  orig_minmem = qa_config.get(constants.BE_MINMEM)
563
  #orig_bridge = qa_config.get("bridge", "xen-br0")
564

    
565
  args = [
566
    ["-B", "%s=128" % constants.BE_MINMEM],
567
    ["-B", "%s=128" % constants.BE_MAXMEM],
568
    ["-B", "%s=%s,%s=%s" % (constants.BE_MINMEM, orig_minmem,
569
                            constants.BE_MAXMEM, orig_maxmem)],
570
    ["-B", "%s=2" % constants.BE_VCPUS],
571
    ["-B", "%s=1" % constants.BE_VCPUS],
572
    ["-B", "%s=%s" % (constants.BE_VCPUS, constants.VALUE_DEFAULT)],
573
    ["-B", "%s=%s" % (constants.BE_ALWAYS_FAILOVER, constants.VALUE_TRUE)],
574
    ["-B", "%s=%s" % (constants.BE_ALWAYS_FAILOVER, constants.VALUE_DEFAULT)],
575

    
576
    ["-H", "%s=%s" % (constants.HV_KERNEL_PATH, test_kernel)],
577
    ["-H", "%s=%s" % (constants.HV_KERNEL_PATH, constants.VALUE_DEFAULT)],
578

    
579
    # TODO: bridge tests
580
    #["--bridge", "xen-br1"],
581
    #["--bridge", orig_bridge],
582
    ]
583

    
584
  if default_hv == constants.HT_XEN_PVM:
585
    args.extend([
586
      ["-H", "%s=%s" % (constants.HV_INITRD_PATH, test_initrd)],
587
      ["-H", "no_%s" % (constants.HV_INITRD_PATH, )],
588
      ["-H", "%s=%s" % (constants.HV_INITRD_PATH, constants.VALUE_DEFAULT)],
589
      ])
590
  elif default_hv == constants.HT_XEN_HVM:
591
    args.extend([
592
      ["-H", "%s=acn" % constants.HV_BOOT_ORDER],
593
      ["-H", "%s=%s" % (constants.HV_BOOT_ORDER, constants.VALUE_DEFAULT)],
594
      ])
595

    
596
  for alist in args:
597
    AssertCommand(["gnt-instance", "modify"] + alist + [instance.name])
598

    
599
  # check no-modify
600
  AssertCommand(["gnt-instance", "modify", instance.name], fail=True)
601

    
602
  # Marking offline while instance is running must fail...
603
  AssertCommand(["gnt-instance", "modify", "--offline", instance.name],
604
                 fail=True)
605

    
606
  # ...while making it online is ok, and should work
607
  AssertCommand(["gnt-instance", "modify", "--online", instance.name])
608

    
609

    
610
@InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
611
def TestInstanceModifyPrimaryAndBack(instance, currentnode, othernode):
612
  """gnt-instance modify --new-primary
613

614
  This will leave the instance on its original primary node, not other node.
615

616
  """
617
  if instance.disk_template != constants.DT_FILE:
618
    print qa_utils.FormatInfo("Test only supported for the file disk template")
619
    return
620

    
621
  cluster_name = qa_config.get("name")
622

    
623
  name = instance.name
624
  current = currentnode.primary
625
  other = othernode.primary
626

    
627
  # FIXME: the qa doesn't have a customizable file storage dir parameter. As
628
  # such for now we use the default.
629
  filestorage = pathutils.DEFAULT_FILE_STORAGE_DIR
630
  disk = os.path.join(filestorage, name)
631

    
632
  AssertCommand(["gnt-instance", "modify", "--new-primary=%s" % other, name],
633
                fail=True)
634
  AssertCommand(["gnt-instance", "shutdown", name])
635
  AssertCommand(["scp", "-oGlobalKnownHostsFile=%s" %
636
                 pathutils.SSH_KNOWN_HOSTS_FILE,
637
                 "-oCheckHostIp=no", "-oStrictHostKeyChecking=yes",
638
                 "-oHashKnownHosts=no", "-oHostKeyAlias=%s" % cluster_name,
639
                 "-r", disk, "%s:%s" % (other, filestorage)])
640
  AssertCommand(["gnt-instance", "modify", "--new-primary=%s" % other, name])
641
  AssertCommand(["gnt-instance", "startup", name])
642

    
643
  # and back
644
  AssertCommand(["gnt-instance", "shutdown", name])
645
  AssertCommand(["rm", "-rf", disk], node=other)
646
  AssertCommand(["gnt-instance", "modify", "--new-primary=%s" % current, name])
647
  AssertCommand(["gnt-instance", "startup", name])
648

    
649

    
650
@InstanceCheck(INST_DOWN, INST_DOWN, FIRST_ARG)
651
def TestInstanceStoppedModify(instance):
652
  """gnt-instance modify (stopped instance)"""
653
  name = instance.name
654

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

    
658
  # Mark instance as offline
659
  AssertCommand(["gnt-instance", "modify", "--offline", name])
660

    
661
  # When the instance is offline shutdown should only work with --force,
662
  # while start should never work
663
  AssertCommand(["gnt-instance", "shutdown", name], fail=True)
664
  AssertCommand(["gnt-instance", "shutdown", "--force", name])
665
  AssertCommand(["gnt-instance", "start", name], fail=True)
666
  AssertCommand(["gnt-instance", "start", "--force", name], fail=True)
667

    
668
  # Also do offline to offline
669
  AssertCommand(["gnt-instance", "modify", "--offline", name])
670

    
671
  # And online again
672
  AssertCommand(["gnt-instance", "modify", "--online", name])
673

    
674

    
675
@InstanceCheck(INST_DOWN, INST_DOWN, FIRST_ARG)
676
def TestInstanceConvertDiskToPlain(instance, inodes):
677
  """gnt-instance modify -t"""
678
  name = instance.name
679

    
680
  template = instance.disk_template
681
  if template != constants.DT_DRBD8:
682
    print qa_utils.FormatInfo("Unsupported template %s, skipping conversion"
683
                              " test" % template)
684
    return
685

    
686
  assert len(inodes) == 2
687
  AssertCommand(["gnt-instance", "modify", "-t", constants.DT_PLAIN, name])
688
  AssertCommand(["gnt-instance", "modify", "-t", constants.DT_DRBD8,
689
                 "-n", inodes[1].primary, name])
690

    
691

    
692
@InstanceCheck(INST_DOWN, INST_DOWN, FIRST_ARG)
693
def TestInstanceGrowDisk(instance):
694
  """gnt-instance grow-disk"""
695
  if qa_config.GetExclusiveStorage():
696
    print qa_utils.FormatInfo("Test not supported with exclusive_storage")
697
    return
698

    
699
  if instance.disk_template == constants.DT_DISKLESS:
700
    print qa_utils.FormatInfo("Test not supported for diskless instances")
701
    return
702

    
703
  name = instance.name
704
  all_size = qa_config.get("disk")
705
  all_grow = qa_config.get("disk-growth")
706

    
707
  if not all_grow:
708
    # missing disk sizes but instance grow disk has been enabled,
709
    # let's set fixed/nomimal growth
710
    all_grow = ["128M" for _ in all_size]
711

    
712
  for idx, (size, grow) in enumerate(zip(all_size, all_grow)):
713
    # succeed in grow by amount
714
    AssertCommand(["gnt-instance", "grow-disk", name, str(idx), grow])
715
    # fail in grow to the old size
716
    AssertCommand(["gnt-instance", "grow-disk", "--absolute", name, str(idx),
717
                   size], fail=True)
718
    # succeed to grow to old size + 2 * growth
719
    int_size = utils.ParseUnit(size)
720
    int_grow = utils.ParseUnit(grow)
721
    AssertCommand(["gnt-instance", "grow-disk", "--absolute", name, str(idx),
722
                   str(int_size + 2 * int_grow)])
723

    
724

    
725
def TestInstanceList():
726
  """gnt-instance list"""
727
  qa_utils.GenericQueryTest("gnt-instance", query.INSTANCE_FIELDS.keys())
728

    
729

    
730
def TestInstanceListFields():
731
  """gnt-instance list-fields"""
732
  qa_utils.GenericQueryFieldsTest("gnt-instance", query.INSTANCE_FIELDS.keys())
733

    
734

    
735
@InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
736
def TestInstanceConsole(instance):
737
  """gnt-instance console"""
738
  AssertCommand(["gnt-instance", "console", "--show-cmd", instance.name])
739

    
740

    
741
@InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
742
def TestReplaceDisks(instance, curr_nodes, other_nodes):
743
  """gnt-instance replace-disks"""
744
  def buildcmd(args):
745
    cmd = ["gnt-instance", "replace-disks"]
746
    cmd.extend(args)
747
    cmd.append(instance.name)
748
    return cmd
749

    
750
  if not IsDiskReplacingSupported(instance):
751
    print qa_utils.FormatInfo("Instance doesn't support disk replacing,"
752
                              " skipping test")
753
    return
754

    
755
  # Currently all supported templates have one primary and one secondary node
756
  assert len(curr_nodes) == 2
757
  snode = curr_nodes[1]
758
  assert len(other_nodes) == 1
759
  othernode = other_nodes[0]
760

    
761
  options = qa_config.get("options", {})
762
  use_ialloc = options.get("use-iallocators", True)
763
  for data in [
764
    ["-p"],
765
    ["-s"],
766
    # A placeholder; the actual command choice depends on use_ialloc
767
    None,
768
    # Restore the original secondary
769
    ["--new-secondary=%s" % snode.primary],
770
    ]:
771
    if data is None:
772
      if use_ialloc:
773
        data = ["-I", constants.DEFAULT_IALLOCATOR_SHORTCUT]
774
      else:
775
        data = ["--new-secondary=%s" % othernode.primary]
776
    AssertCommand(buildcmd(data))
777

    
778
  AssertCommand(buildcmd(["-a"]))
779
  AssertCommand(["gnt-instance", "stop", instance.name])
780
  AssertCommand(buildcmd(["-a"]), fail=True)
781
  AssertCommand(["gnt-instance", "activate-disks", instance.name])
782
  AssertCommand(["gnt-instance", "activate-disks", "--wait-for-sync",
783
                 instance.name])
784
  AssertCommand(buildcmd(["-a"]))
785
  AssertCommand(["gnt-instance", "start", instance.name])
786

    
787

    
788
def _AssertRecreateDisks(cmdargs, instance, fail=False, check=True,
789
                         destroy=True):
790
  """Execute gnt-instance recreate-disks and check the result
791

792
  @param cmdargs: Arguments (instance name excluded)
793
  @param instance: Instance to operate on
794
  @param fail: True if the command is expected to fail
795
  @param check: If True and fail is False, check that the disks work
796
  @prama destroy: If True, destroy the old disks first
797

798
  """
799
  if destroy:
800
    _DestroyInstanceDisks(instance)
801
  AssertCommand((["gnt-instance", "recreate-disks"] + cmdargs +
802
                 [instance.name]), fail)
803
  if not fail and check:
804
    # Quick check that the disks are there
805
    AssertCommand(["gnt-instance", "activate-disks", instance.name])
806
    AssertCommand(["gnt-instance", "activate-disks", "--wait-for-sync",
807
                   instance.name])
808
    AssertCommand(["gnt-instance", "deactivate-disks", instance.name])
809

    
810

    
811
@InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
812
def TestRecreateDisks(instance, inodes, othernodes):
813
  """gnt-instance recreate-disks
814

815
  @param instance: Instance to work on
816
  @param inodes: List of the current nodes of the instance
817
  @param othernodes: list/tuple of nodes where to temporarily recreate disks
818

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

    
849

    
850
@InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
851
def TestInstanceExport(instance, node):
852
  """gnt-backup export -n ..."""
853
  name = instance.name
854
  AssertCommand(["gnt-backup", "export", "-n", node.primary, name])
855
  return qa_utils.ResolveInstanceName(name)
856

    
857

    
858
@InstanceCheck(None, INST_DOWN, FIRST_ARG)
859
def TestInstanceExportWithRemove(instance, node):
860
  """gnt-backup export --remove-instance"""
861
  AssertCommand(["gnt-backup", "export", "-n", node.primary,
862
                 "--remove-instance", instance.name])
863

    
864

    
865
@InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
866
def TestInstanceExportNoTarget(instance):
867
  """gnt-backup export (without target node, should fail)"""
868
  AssertCommand(["gnt-backup", "export", instance.name], fail=True)
869

    
870

    
871
@InstanceCheck(None, INST_DOWN, FIRST_ARG)
872
def TestInstanceImport(newinst, node, expnode, name):
873
  """gnt-backup import"""
874
  templ = constants.DT_PLAIN
875
  cmd = (["gnt-backup", "import",
876
          "--disk-template=%s" % templ,
877
          "--no-ip-check",
878
          "--src-node=%s" % expnode.primary,
879
          "--src-dir=%s/%s" % (pathutils.EXPORT_DIR, name),
880
          "--node=%s" % node.primary] +
881
         _GetGenericAddParameters(newinst, templ,
882
                                  force_mac=constants.VALUE_GENERATE))
883
  cmd.append(newinst.name)
884
  AssertCommand(cmd)
885
  newinst.SetDiskTemplate(templ)
886

    
887

    
888
def TestBackupList(expnode):
889
  """gnt-backup list"""
890
  AssertCommand(["gnt-backup", "list", "--node=%s" % expnode.primary])
891

    
892
  qa_utils.GenericQueryTest("gnt-backup", query.EXPORT_FIELDS.keys(),
893
                            namefield=None, test_unknown=False)
894

    
895

    
896
def TestBackupListFields():
897
  """gnt-backup list-fields"""
898
  qa_utils.GenericQueryFieldsTest("gnt-backup", query.EXPORT_FIELDS.keys())
899

    
900

    
901
def TestRemoveInstanceOfflineNode(instance, snode, set_offline, set_online):
902
  """gnt-instance remove with an off-line node
903

904
  @param instance: instance
905
  @param snode: secondary node, to be set offline
906
  @param set_offline: function to call to set the node off-line
907
  @param set_online: function to call to set the node on-line
908

909
  """
910
  info = _GetInstanceInfo(instance.name)
911
  set_offline(snode)
912
  try:
913
    TestInstanceRemove(instance)
914
  finally:
915
    set_online(snode)
916

    
917
  # Clean up the disks on the offline node, if necessary
918
  if instance.disk_template not in constants.DTS_EXT_MIRROR:
919
    # FIXME: abstract the cleanup inside the disks
920
    if info["storage-type"] == constants.ST_LVM_VG:
921
      for minor in info["drbd-minors"][snode.primary]:
922
        AssertCommand(["drbdsetup", str(minor), "down"], node=snode)
923
      AssertCommand(["lvremove", "-f"] + info["volumes"], node=snode)
924
    elif info["storage-type"] == constants.ST_FILE:
925
      filestorage = pathutils.DEFAULT_FILE_STORAGE_DIR
926
      disk = os.path.join(filestorage, instance.name)
927
      AssertCommand(["rm", "-rf", disk], node=snode)