Statistics
| Branch: | Tag: | Revision:

root / qa / qa_instance.py @ fa84c8a4

History | View | Annotate | Download (26.6 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 re
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, fail=False):
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, fail=fail)
77

    
78
    if not fail:
79
      _CheckSsconfInstanceList(instance["name"])
80
      qa_config.SetInstanceTemplate(instance, disk_template)
81

    
82
      return instance
83
  except:
84
    qa_config.ReleaseInstance(instance)
85
    raise
86

    
87
  # Handle the case where creation is expected to fail
88
  assert fail
89
  qa_config.ReleaseInstance(instance)
90
  return None
91

    
92

    
93
def _GetInstanceInfo(instance):
94
  """Return information about the actual state of an instance.
95

96
  @type instance: string
97
  @param instance: the instance name
98
  @return: a dictionary with the following keys:
99
      - "nodes": instance nodes, a list of strings
100
      - "volumes": instance volume IDs, a list of strings
101
      - "drbd-minors": DRBD minors used by the instance, a dictionary where
102
        keys are nodes, and values are lists of integers (or an empty
103
        dictionary for non-DRBD instances)
104

105
  """
106
  master = qa_config.GetMasterNode()
107
  infocmd = utils.ShellQuoteArgs(["gnt-instance", "info", instance])
108
  info_out = qa_utils.GetCommandOutput(master["primary"], infocmd)
109
  re_node = re.compile(r"^\s+-\s+(?:primary|secondaries):\s+(\S.+)$")
110
  node_elem = r"([^,()]+)(?:\s+\([^)]+\))?"
111
  # re_nodelist matches a list of nodes returned by gnt-instance info, e.g.:
112
  #  node1.fqdn
113
  #  node2.fqdn,node3.fqdn
114
  #  node4.fqdn (group mygroup, group UUID 01234567-abcd-0123-4567-0123456789ab)
115
  # FIXME This works with no more than 2 secondaries
116
  re_nodelist = re.compile(node_elem + "(?:," + node_elem + ")?$")
117
  re_vol = re.compile(r"^\s+logical_id:\s+(\S+)$")
118
  re_drbdnode = re.compile(r"^\s+node[AB]:\s+([^\s,]+),\s+minor=([0-9]+)$")
119
  nodes = []
120
  vols = []
121
  drbd_min = {}
122
  for line in info_out.splitlines():
123
    m = re_node.match(line)
124
    if m:
125
      nodestr = m.group(1)
126
      m2 = re_nodelist.match(nodestr)
127
      if m2:
128
        nodes.extend(filter(None, m2.groups()))
129
      else:
130
        nodes.append(nodestr)
131
    m = re_vol.match(line)
132
    if m:
133
      vols.append(m.group(1))
134
    m = re_drbdnode.match(line)
135
    if m:
136
      node = m.group(1)
137
      minor = int(m.group(2))
138
      if drbd_min.get(node) is not None:
139
        drbd_min[node].append(minor)
140
      else:
141
        drbd_min[node] = [minor]
142
  assert vols
143
  assert nodes
144
  return {"nodes": nodes, "volumes": vols, "drbd-minors": drbd_min}
145

    
146

    
147
def _DestroyInstanceVolumes(instance):
148
  """Remove all the LVM volumes of an instance.
149

150
  This is used to simulate HW errors (dead nodes, broken disks...); the
151
  configuration of the instance is not affected.
152
  @type instance: dictionary
153
  @param instance: the instance
154

155
  """
156
  info = _GetInstanceInfo(instance["name"])
157
  vols = info["volumes"]
158
  for node in info["nodes"]:
159
    AssertCommand(["lvremove", "-f"] + vols, node=node)
160

    
161

    
162
def _GetInstanceField(instance, field):
163
  """Get the value of a field of an instance.
164

165
  @type instance: string
166
  @param instance: Instance name
167
  @type field: string
168
  @param field: Name of the field
169
  @rtype: string
170

171
  """
172
  master = qa_config.GetMasterNode()
173
  infocmd = utils.ShellQuoteArgs(["gnt-instance", "list", "--no-headers",
174
                                  "--units", "m", "-o", field, instance])
175
  return qa_utils.GetCommandOutput(master["primary"], infocmd).strip()
176

    
177

    
178
def _GetBoolInstanceField(instance, field):
179
  """Get the Boolean value of a field of an instance.
180

181
  @type instance: string
182
  @param instance: Instance name
183
  @type field: string
184
  @param field: Name of the field
185
  @rtype: bool
186

187
  """
188
  info_out = _GetInstanceField(instance, field)
189
  if info_out == "Y":
190
    return True
191
  elif info_out == "N":
192
    return False
193
  else:
194
    raise qa_error.Error("Field %s of instance %s has a non-Boolean value:"
195
                         " %s" % (field, instance, info_out))
196

    
197

    
198
def _GetNumInstanceField(instance, field):
199
  """Get a numeric value of a field of an instance.
200

201
  @type instance: string
202
  @param instance: Instance name
203
  @type field: string
204
  @param field: Name of the field
205
  @rtype: int or float
206

207
  """
208
  info_out = _GetInstanceField(instance, field)
209
  try:
210
    ret = int(info_out)
211
  except ValueError:
212
    try:
213
      ret = float(info_out)
214
    except ValueError:
215
      raise qa_error.Error("Field %s of instance %s has a non-numeric value:"
216
                           " %s" % (field, instance, info_out))
217
  return ret
218

    
219

    
220
def GetInstanceSpec(instance, spec):
221
  """Return the current spec for the given parameter.
222

223
  @type instance: string
224
  @param instance: Instance name
225
  @type spec: string
226
  @param spec: one of the supported parameters: "mem-size", "cpu-count",
227
      "disk-count", "disk-size", "nic-count"
228
  @rtype: tuple
229
  @return: (minspec, maxspec); minspec and maxspec can be different only for
230
      memory and disk size
231

232
  """
233
  specmap = {
234
    "mem-size": ["be/minmem", "be/maxmem"],
235
    "cpu-count": ["vcpus"],
236
    "disk-count": ["disk.count"],
237
    "disk-size": ["disk.size/ "],
238
    "nic-count": ["nic.count"],
239
    }
240
  # For disks, first we need the number of disks
241
  if spec == "disk-size":
242
    (numdisk, _) = GetInstanceSpec(instance, "disk-count")
243
    fields = ["disk.size/%s" % k for k in range(0, numdisk)]
244
  else:
245
    assert spec in specmap, "%s not in %s" % (spec, specmap)
246
    fields = specmap[spec]
247
  values = [_GetNumInstanceField(instance, f) for f in fields]
248
  return (min(values), max(values))
249

    
250

    
251
def IsFailoverSupported(instance):
252
  templ = qa_config.GetInstanceTemplate(instance)
253
  return templ in constants.DTS_MIRRORED
254

    
255

    
256
def IsMigrationSupported(instance):
257
  templ = qa_config.GetInstanceTemplate(instance)
258
  return templ in constants.DTS_MIRRORED
259

    
260

    
261
def IsDiskReplacingSupported(instance):
262
  templ = qa_config.GetInstanceTemplate(instance)
263
  return templ == constants.DT_DRBD8
264

    
265

    
266
def TestInstanceAddWithPlainDisk(nodes, fail=False):
267
  """gnt-instance add -t plain"""
268
  assert len(nodes) == 1
269
  instance = _DiskTest(nodes[0]["primary"], constants.DT_PLAIN, fail=fail)
270
  if not fail:
271
    qa_utils.RunInstanceCheck(instance, True)
272
  return instance
273

    
274

    
275
@InstanceCheck(None, INST_UP, RETURN_VALUE)
276
def TestInstanceAddWithDrbdDisk(nodes):
277
  """gnt-instance add -t drbd"""
278
  assert len(nodes) == 2
279
  return _DiskTest(":".join(map(operator.itemgetter("primary"), nodes)),
280
                   "drbd")
281

    
282

    
283
@InstanceCheck(None, INST_DOWN, FIRST_ARG)
284
def TestInstanceRemove(instance):
285
  """gnt-instance remove"""
286
  AssertCommand(["gnt-instance", "remove", "-f", instance["name"]])
287

    
288
  qa_config.ReleaseInstance(instance)
289

    
290

    
291
@InstanceCheck(INST_DOWN, INST_UP, FIRST_ARG)
292
def TestInstanceStartup(instance):
293
  """gnt-instance startup"""
294
  AssertCommand(["gnt-instance", "startup", instance["name"]])
295

    
296

    
297
@InstanceCheck(INST_UP, INST_DOWN, FIRST_ARG)
298
def TestInstanceShutdown(instance):
299
  """gnt-instance shutdown"""
300
  AssertCommand(["gnt-instance", "shutdown", instance["name"]])
301

    
302

    
303
@InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
304
def TestInstanceReboot(instance):
305
  """gnt-instance reboot"""
306
  options = qa_config.get("options", {})
307
  reboot_types = options.get("reboot-types", constants.REBOOT_TYPES)
308
  name = instance["name"]
309
  for rtype in reboot_types:
310
    AssertCommand(["gnt-instance", "reboot", "--type=%s" % rtype, name])
311

    
312
  AssertCommand(["gnt-instance", "shutdown", name])
313
  qa_utils.RunInstanceCheck(instance, False)
314
  AssertCommand(["gnt-instance", "reboot", name])
315

    
316
  master = qa_config.GetMasterNode()
317
  cmd = ["gnt-instance", "list", "--no-headers", "-o", "status", name]
318
  result_output = qa_utils.GetCommandOutput(master["primary"],
319
                                            utils.ShellQuoteArgs(cmd))
320
  AssertEqual(result_output.strip(), constants.INSTST_RUNNING)
321

    
322

    
323
@InstanceCheck(INST_DOWN, INST_DOWN, FIRST_ARG)
324
def TestInstanceReinstall(instance):
325
  """gnt-instance reinstall"""
326
  AssertCommand(["gnt-instance", "reinstall", "-f", instance["name"]])
327

    
328
  # Test with non-existant OS definition
329
  AssertCommand(["gnt-instance", "reinstall", "-f",
330
                 "--os-type=NonExistantOsForQa",
331
                 instance["name"]],
332
                fail=True)
333

    
334

    
335
def _ReadSsconfInstanceList():
336
  """Reads ssconf_instance_list from the master node.
337

338
  """
339
  master = qa_config.GetMasterNode()
340

    
341
  cmd = ["cat", utils.PathJoin(pathutils.DATA_DIR,
342
                               "ssconf_%s" % constants.SS_INSTANCE_LIST)]
343

    
344
  return qa_utils.GetCommandOutput(master["primary"],
345
                                   utils.ShellQuoteArgs(cmd)).splitlines()
346

    
347

    
348
def _CheckSsconfInstanceList(instance):
349
  """Checks if a certain instance is in the ssconf instance list.
350

351
  @type instance: string
352
  @param instance: Instance name
353

354
  """
355
  AssertIn(qa_utils.ResolveInstanceName(instance),
356
           _ReadSsconfInstanceList())
357

    
358

    
359
@InstanceCheck(INST_DOWN, INST_DOWN, FIRST_ARG)
360
def TestInstanceRenameAndBack(rename_source, rename_target):
361
  """gnt-instance rename
362

363
  This must leave the instance with the original name, not the target
364
  name.
365

366
  """
367
  _CheckSsconfInstanceList(rename_source)
368

    
369
  # first do a rename to a different actual name, expecting it to fail
370
  qa_utils.AddToEtcHosts(["meeeeh-not-exists", rename_target])
371
  try:
372
    AssertCommand(["gnt-instance", "rename", rename_source, rename_target],
373
                  fail=True)
374
    _CheckSsconfInstanceList(rename_source)
375
  finally:
376
    qa_utils.RemoveFromEtcHosts(["meeeeh-not-exists", rename_target])
377

    
378
  # Check instance volume tags correctly updated
379
  # FIXME: this is LVM specific!
380
  info = _GetInstanceInfo(rename_source)
381
  tags_cmd = ("lvs -o tags --noheadings %s | grep " %
382
              (" ".join(info["volumes"]), ))
383

    
384
  # and now rename instance to rename_target...
385
  AssertCommand(["gnt-instance", "rename", rename_source, rename_target])
386
  _CheckSsconfInstanceList(rename_target)
387
  qa_utils.RunInstanceCheck(rename_source, False)
388
  qa_utils.RunInstanceCheck(rename_target, False)
389

    
390
  # NOTE: tags might not be the exactly as the instance name, due to
391
  # charset restrictions; hence the test might be flaky
392
  if rename_source != rename_target:
393
    for node in info["nodes"]:
394
      AssertCommand(tags_cmd + rename_source, node=node, fail=True)
395
      AssertCommand(tags_cmd + rename_target, node=node, fail=False)
396

    
397
  # and back
398
  AssertCommand(["gnt-instance", "rename", rename_target, rename_source])
399
  _CheckSsconfInstanceList(rename_source)
400
  qa_utils.RunInstanceCheck(rename_target, False)
401

    
402
  if rename_source != rename_target:
403
    for node in info["nodes"]:
404
      AssertCommand(tags_cmd + rename_source, node=node, fail=False)
405
      AssertCommand(tags_cmd + rename_target, node=node, fail=True)
406

    
407

    
408
@InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
409
def TestInstanceFailover(instance):
410
  """gnt-instance failover"""
411
  if not IsFailoverSupported(instance):
412
    print qa_utils.FormatInfo("Instance doesn't support failover, skipping"
413
                              " test")
414
    return
415

    
416
  cmd = ["gnt-instance", "failover", "--force", instance["name"]]
417

    
418
  # failover ...
419
  AssertCommand(cmd)
420
  qa_utils.RunInstanceCheck(instance, True)
421

    
422
  # ... and back
423
  AssertCommand(cmd)
424

    
425

    
426
@InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
427
def TestInstanceMigrate(instance, toggle_always_failover=True):
428
  """gnt-instance migrate"""
429
  if not IsMigrationSupported(instance):
430
    print qa_utils.FormatInfo("Instance doesn't support migration, skipping"
431
                              " test")
432
    return
433

    
434
  cmd = ["gnt-instance", "migrate", "--force", instance["name"]]
435
  af_par = constants.BE_ALWAYS_FAILOVER
436
  af_field = "be/" + constants.BE_ALWAYS_FAILOVER
437
  af_init_val = _GetBoolInstanceField(instance["name"], af_field)
438

    
439
  # migrate ...
440
  AssertCommand(cmd)
441
  # TODO: Verify the choice between failover and migration
442
  qa_utils.RunInstanceCheck(instance, True)
443

    
444
  # ... and back (possibly with always_failover toggled)
445
  if toggle_always_failover:
446
    AssertCommand(["gnt-instance", "modify", "-B",
447
                   ("%s=%s" % (af_par, not af_init_val)),
448
                   instance["name"]])
449
  AssertCommand(cmd)
450
  # TODO: Verify the choice between failover and migration
451
  qa_utils.RunInstanceCheck(instance, True)
452
  if toggle_always_failover:
453
    AssertCommand(["gnt-instance", "modify", "-B",
454
                   ("%s=%s" % (af_par, af_init_val)), instance["name"]])
455

    
456
  # TODO: Split into multiple tests
457
  AssertCommand(["gnt-instance", "shutdown", instance["name"]])
458
  qa_utils.RunInstanceCheck(instance, False)
459
  AssertCommand(cmd, fail=True)
460
  AssertCommand(["gnt-instance", "migrate", "--force", "--allow-failover",
461
                 instance["name"]])
462
  AssertCommand(["gnt-instance", "start", instance["name"]])
463
  AssertCommand(cmd)
464
  # @InstanceCheck enforces the check that the instance is running
465
  qa_utils.RunInstanceCheck(instance, True)
466

    
467
  AssertCommand(["gnt-instance", "modify", "-B",
468
                 ("%s=%s" %
469
                  (constants.BE_ALWAYS_FAILOVER, constants.VALUE_TRUE)),
470
                 instance["name"]])
471

    
472
  AssertCommand(cmd)
473
  qa_utils.RunInstanceCheck(instance, True)
474
  # TODO: Verify that a failover has been done instead of a migration
475

    
476
  # TODO: Verify whether the default value is restored here (not hardcoded)
477
  AssertCommand(["gnt-instance", "modify", "-B",
478
                 ("%s=%s" %
479
                  (constants.BE_ALWAYS_FAILOVER, constants.VALUE_FALSE)),
480
                 instance["name"]])
481

    
482
  AssertCommand(cmd)
483
  qa_utils.RunInstanceCheck(instance, True)
484

    
485

    
486
def TestInstanceInfo(instance):
487
  """gnt-instance info"""
488
  AssertCommand(["gnt-instance", "info", instance["name"]])
489

    
490

    
491
@InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
492
def TestInstanceModify(instance):
493
  """gnt-instance modify"""
494
  default_hv = qa_config.GetDefaultHypervisor()
495

    
496
  # Assume /sbin/init exists on all systems
497
  test_kernel = "/sbin/init"
498
  test_initrd = test_kernel
499

    
500
  orig_maxmem = qa_config.get(constants.BE_MAXMEM)
501
  orig_minmem = qa_config.get(constants.BE_MINMEM)
502
  #orig_bridge = qa_config.get("bridge", "xen-br0")
503

    
504
  args = [
505
    ["-B", "%s=128" % constants.BE_MINMEM],
506
    ["-B", "%s=128" % constants.BE_MAXMEM],
507
    ["-B", "%s=%s,%s=%s" % (constants.BE_MINMEM, orig_minmem,
508
                            constants.BE_MAXMEM, orig_maxmem)],
509
    ["-B", "%s=2" % constants.BE_VCPUS],
510
    ["-B", "%s=1" % constants.BE_VCPUS],
511
    ["-B", "%s=%s" % (constants.BE_VCPUS, constants.VALUE_DEFAULT)],
512
    ["-B", "%s=%s" % (constants.BE_ALWAYS_FAILOVER, constants.VALUE_TRUE)],
513
    ["-B", "%s=%s" % (constants.BE_ALWAYS_FAILOVER, constants.VALUE_DEFAULT)],
514

    
515
    ["-H", "%s=%s" % (constants.HV_KERNEL_PATH, test_kernel)],
516
    ["-H", "%s=%s" % (constants.HV_KERNEL_PATH, constants.VALUE_DEFAULT)],
517

    
518
    # TODO: bridge tests
519
    #["--bridge", "xen-br1"],
520
    #["--bridge", orig_bridge],
521
    ]
522

    
523
  if default_hv == constants.HT_XEN_PVM:
524
    args.extend([
525
      ["-H", "%s=%s" % (constants.HV_INITRD_PATH, test_initrd)],
526
      ["-H", "no_%s" % (constants.HV_INITRD_PATH, )],
527
      ["-H", "%s=%s" % (constants.HV_INITRD_PATH, constants.VALUE_DEFAULT)],
528
      ])
529
  elif default_hv == constants.HT_XEN_HVM:
530
    args.extend([
531
      ["-H", "%s=acn" % constants.HV_BOOT_ORDER],
532
      ["-H", "%s=%s" % (constants.HV_BOOT_ORDER, constants.VALUE_DEFAULT)],
533
      ])
534

    
535
  for alist in args:
536
    AssertCommand(["gnt-instance", "modify"] + alist + [instance["name"]])
537

    
538
  # check no-modify
539
  AssertCommand(["gnt-instance", "modify", instance["name"]], fail=True)
540

    
541
  # Marking offline while instance is running must fail...
542
  AssertCommand(["gnt-instance", "modify", "--offline", instance["name"]],
543
                 fail=True)
544

    
545
  # ...while making it online is ok, and should work
546
  AssertCommand(["gnt-instance", "modify", "--online", instance["name"]])
547

    
548

    
549
@InstanceCheck(INST_DOWN, INST_DOWN, FIRST_ARG)
550
def TestInstanceStoppedModify(instance):
551
  """gnt-instance modify (stopped instance)"""
552
  name = instance["name"]
553

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

    
557
  # Mark instance as offline
558
  AssertCommand(["gnt-instance", "modify", "--offline", name])
559

    
560
  # When the instance is offline shutdown should only work with --force,
561
  # while start should never work
562
  AssertCommand(["gnt-instance", "shutdown", name], fail=True)
563
  AssertCommand(["gnt-instance", "shutdown", "--force", name])
564
  AssertCommand(["gnt-instance", "start", name], fail=True)
565
  AssertCommand(["gnt-instance", "start", "--force", name], fail=True)
566

    
567
  # Also do offline to offline
568
  AssertCommand(["gnt-instance", "modify", "--offline", name])
569

    
570
  # And online again
571
  AssertCommand(["gnt-instance", "modify", "--online", name])
572

    
573

    
574
@InstanceCheck(INST_DOWN, INST_DOWN, FIRST_ARG)
575
def TestInstanceConvertDiskToPlain(instance, inodes):
576
  """gnt-instance modify -t"""
577
  name = instance["name"]
578
  template = qa_config.GetInstanceTemplate(instance)
579
  if template != "drbd":
580
    print qa_utils.FormatInfo("Unsupported template %s, skipping conversion"
581
                              " test" % template)
582
    return
583
  assert len(inodes) == 2
584
  AssertCommand(["gnt-instance", "modify", "-t", "plain", name])
585
  AssertCommand(["gnt-instance", "modify", "-t", "drbd",
586
                 "-n", inodes[1]["primary"], name])
587

    
588

    
589
@InstanceCheck(INST_DOWN, INST_DOWN, FIRST_ARG)
590
def TestInstanceGrowDisk(instance):
591
  """gnt-instance grow-disk"""
592
  if qa_config.GetExclusiveStorage():
593
    print qa_utils.FormatInfo("Test not supported with exclusive_storage")
594
    return
595
  name = instance["name"]
596
  all_size = qa_config.get("disk")
597
  all_grow = qa_config.get("disk-growth")
598
  if not all_grow:
599
    # missing disk sizes but instance grow disk has been enabled,
600
    # let's set fixed/nomimal growth
601
    all_grow = ["128M" for _ in all_size]
602
  for idx, (size, grow) in enumerate(zip(all_size, all_grow)):
603
    # succeed in grow by amount
604
    AssertCommand(["gnt-instance", "grow-disk", name, str(idx), grow])
605
    # fail in grow to the old size
606
    AssertCommand(["gnt-instance", "grow-disk", "--absolute", name, str(idx),
607
                   size], fail=True)
608
    # succeed to grow to old size + 2 * growth
609
    int_size = utils.ParseUnit(size)
610
    int_grow = utils.ParseUnit(grow)
611
    AssertCommand(["gnt-instance", "grow-disk", "--absolute", name, str(idx),
612
                   str(int_size + 2 * int_grow)])
613

    
614

    
615
def TestInstanceList():
616
  """gnt-instance list"""
617
  qa_utils.GenericQueryTest("gnt-instance", query.INSTANCE_FIELDS.keys())
618

    
619

    
620
def TestInstanceListFields():
621
  """gnt-instance list-fields"""
622
  qa_utils.GenericQueryFieldsTest("gnt-instance", query.INSTANCE_FIELDS.keys())
623

    
624

    
625
@InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
626
def TestInstanceConsole(instance):
627
  """gnt-instance console"""
628
  AssertCommand(["gnt-instance", "console", "--show-cmd", instance["name"]])
629

    
630

    
631
@InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
632
def TestReplaceDisks(instance, curr_nodes, other_nodes):
633
  """gnt-instance replace-disks"""
634
  def buildcmd(args):
635
    cmd = ["gnt-instance", "replace-disks"]
636
    cmd.extend(args)
637
    cmd.append(instance["name"])
638
    return cmd
639

    
640
  if not IsDiskReplacingSupported(instance):
641
    print qa_utils.FormatInfo("Instance doesn't support disk replacing,"
642
                              " skipping test")
643
    return
644

    
645
  # Currently all supported templates have one primary and one secondary node
646
  assert len(curr_nodes) == 2
647
  snode = curr_nodes[1]
648
  assert len(other_nodes) == 1
649
  othernode = other_nodes[0]
650

    
651
  options = qa_config.get("options", {})
652
  use_ialloc = options.get("use-iallocators", True)
653
  for data in [
654
    ["-p"],
655
    ["-s"],
656
    # A placeholder; the actual command choice depends on use_ialloc
657
    None,
658
    # Restore the original secondary
659
    ["--new-secondary=%s" % snode["primary"]],
660
    ]:
661
    if data is None:
662
      if use_ialloc:
663
        data = ["-I", constants.DEFAULT_IALLOCATOR_SHORTCUT]
664
      else:
665
        data = ["--new-secondary=%s" % othernode["primary"]]
666
    AssertCommand(buildcmd(data))
667

    
668
  AssertCommand(buildcmd(["-a"]))
669
  AssertCommand(["gnt-instance", "stop", instance["name"]])
670
  AssertCommand(buildcmd(["-a"]), fail=True)
671
  AssertCommand(["gnt-instance", "activate-disks", instance["name"]])
672
  AssertCommand(["gnt-instance", "activate-disks", "--wait-for-sync",
673
                 instance["name"]])
674
  AssertCommand(buildcmd(["-a"]))
675
  AssertCommand(["gnt-instance", "start", instance["name"]])
676

    
677

    
678
def _AssertRecreateDisks(cmdargs, instance, fail=False, check=True,
679
                         destroy=True):
680
  """Execute gnt-instance recreate-disks and check the result
681

682
  @param cmdargs: Arguments (instance name excluded)
683
  @param instance: Instance to operate on
684
  @param fail: True if the command is expected to fail
685
  @param check: If True and fail is False, check that the disks work
686
  @prama destroy: If True, destroy the old disks first
687

688
  """
689
  if destroy:
690
    _DestroyInstanceVolumes(instance)
691
  AssertCommand((["gnt-instance", "recreate-disks"] + cmdargs +
692
                 [instance["name"]]), fail)
693
  if not fail and check:
694
    # Quick check that the disks are there
695
    AssertCommand(["gnt-instance", "activate-disks", instance["name"]])
696
    AssertCommand(["gnt-instance", "activate-disks", "--wait-for-sync",
697
                   instance["name"]])
698
    AssertCommand(["gnt-instance", "deactivate-disks", instance["name"]])
699

    
700

    
701
@InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
702
def TestRecreateDisks(instance, inodes, othernodes):
703
  """gnt-instance recreate-disks
704

705
  @param instance: Instance to work on
706
  @param inodes: List of the current nodes of the instance
707
  @param othernodes: list/tuple of nodes where to temporarily recreate disks
708

709
  """
710
  options = qa_config.get("options", {})
711
  use_ialloc = options.get("use-iallocators", True)
712
  other_seq = ":".join([n["primary"] for n in othernodes])
713
  orig_seq = ":".join([n["primary"] for n in inodes])
714
  # These fail because the instance is running
715
  _AssertRecreateDisks(["-n", other_seq], instance, fail=True, destroy=False)
716
  if use_ialloc:
717
    _AssertRecreateDisks(["-I", "hail"], instance, fail=True, destroy=False)
718
  else:
719
    _AssertRecreateDisks(["-n", other_seq], instance, fail=True, destroy=False)
720
  AssertCommand(["gnt-instance", "stop", instance["name"]])
721
  # Disks exist: this should fail
722
  _AssertRecreateDisks([], instance, fail=True, destroy=False)
723
  # Recreate disks in place
724
  _AssertRecreateDisks([], instance)
725
  # Move disks away
726
  if use_ialloc:
727
    _AssertRecreateDisks(["-I", "hail"], instance)
728
    # Move disks somewhere else
729
    _AssertRecreateDisks(["-I", constants.DEFAULT_IALLOCATOR_SHORTCUT],
730
                         instance)
731
  else:
732
    _AssertRecreateDisks(["-n", other_seq], instance)
733
  # Move disks back
734
  _AssertRecreateDisks(["-n", orig_seq], instance, check=False)
735
  # This and InstanceCheck decoration check that the disks are working
736
  AssertCommand(["gnt-instance", "reinstall", "-f", instance["name"]])
737
  AssertCommand(["gnt-instance", "start", instance["name"]])
738

    
739

    
740
@InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
741
def TestInstanceExport(instance, node):
742
  """gnt-backup export -n ..."""
743
  name = instance["name"]
744
  AssertCommand(["gnt-backup", "export", "-n", node["primary"], name])
745
  return qa_utils.ResolveInstanceName(name)
746

    
747

    
748
@InstanceCheck(None, INST_DOWN, FIRST_ARG)
749
def TestInstanceExportWithRemove(instance, node):
750
  """gnt-backup export --remove-instance"""
751
  AssertCommand(["gnt-backup", "export", "-n", node["primary"],
752
                 "--remove-instance", instance["name"]])
753
  qa_config.ReleaseInstance(instance)
754

    
755

    
756
@InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
757
def TestInstanceExportNoTarget(instance):
758
  """gnt-backup export (without target node, should fail)"""
759
  AssertCommand(["gnt-backup", "export", instance["name"]], fail=True)
760

    
761

    
762
@InstanceCheck(None, INST_DOWN, FIRST_ARG)
763
def TestInstanceImport(newinst, node, expnode, name):
764
  """gnt-backup import"""
765
  templ = constants.DT_PLAIN
766
  cmd = (["gnt-backup", "import",
767
          "--disk-template=%s" % templ,
768
          "--no-ip-check",
769
          "--src-node=%s" % expnode["primary"],
770
          "--src-dir=%s/%s" % (pathutils.EXPORT_DIR, name),
771
          "--node=%s" % node["primary"]] +
772
         _GetGenericAddParameters(newinst, force_mac=constants.VALUE_GENERATE))
773
  cmd.append(newinst["name"])
774
  AssertCommand(cmd)
775
  qa_config.SetInstanceTemplate(newinst, templ)
776

    
777

    
778
def TestBackupList(expnode):
779
  """gnt-backup list"""
780
  AssertCommand(["gnt-backup", "list", "--node=%s" % expnode["primary"]])
781

    
782
  qa_utils.GenericQueryTest("gnt-backup", query.EXPORT_FIELDS.keys(),
783
                            namefield=None, test_unknown=False)
784

    
785

    
786
def TestBackupListFields():
787
  """gnt-backup list-fields"""
788
  qa_utils.GenericQueryFieldsTest("gnt-backup", query.EXPORT_FIELDS.keys())
789

    
790

    
791
def TestRemoveInstanceOfflineNode(instance, snode, set_offline, set_online):
792
  """gtn-instance remove with an off-line node
793

794
  @param instance: instance
795
  @param snode: secondary node, to be set offline
796
  @param set_offline: function to call to set the node off-line
797
  @param set_online: function to call to set the node on-line
798

799
  """
800
  info = _GetInstanceInfo(instance["name"])
801
  set_offline(snode)
802
  try:
803
    TestInstanceRemove(instance)
804
  finally:
805
    set_online(snode)
806
  # Clean up the disks on the offline node
807
  for minor in info["drbd-minors"][snode["primary"]]:
808
    AssertCommand(["drbdsetup", str(minor), "down"], node=snode)
809
  AssertCommand(["lvremove", "-f"] + info["volumes"], node=snode)