Statistics
| Branch: | Tag: | Revision:

root / qa / qa_instance.py @ ab4832d1

History | View | Annotate | Download (26.4 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):
67
  instance = qa_config.AcquireInstance()
68
  try:
69
    cmd = (["gnt-instance", "add",
70
            "--os-type=%s" % qa_config.get("os"),
71
            "--disk-template=%s" % disk_template,
72
            "--node=%s" % node] +
73
           _GetGenericAddParameters(instance))
74
    cmd.append(instance["name"])
75

    
76
    AssertCommand(cmd)
77

    
78
    _CheckSsconfInstanceList(instance["name"])
79
    qa_config.SetInstanceTemplate(instance, disk_template)
80

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

    
86

    
87
def _GetInstanceInfo(instance):
88
  """Return information about the actual state of an instance.
89

90
  @type instance: string
91
  @param instance: the instance name
92
  @return: a dictionary with the following keys:
93
      - "nodes": instance nodes, a list of strings
94
      - "volumes": instance volume IDs, a list of strings
95
      - "drbd-minors": DRBD minors used by the instance, a dictionary where
96
        keys are nodes, and values are lists of integers (or an empty
97
        dictionary for non-DRBD instances)
98

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

    
140

    
141
def _DestroyInstanceVolumes(instance):
142
  """Remove all the LVM volumes of an instance.
143

144
  This is used to simulate HW errors (dead nodes, broken disks...); the
145
  configuration of the instance is not affected.
146
  @type instance: dictionary
147
  @param instance: the instance
148

149
  """
150
  info = _GetInstanceInfo(instance["name"])
151
  vols = info["volumes"]
152
  for node in info["nodes"]:
153
    AssertCommand(["lvremove", "-f"] + vols, node=node)
154

    
155

    
156
def _GetInstanceField(instance, field):
157
  """Get the value of a field of an instance.
158

159
  @type instance: string
160
  @param instance: Instance name
161
  @type field: string
162
  @param field: Name of the field
163
  @rtype: string
164

165
  """
166
  master = qa_config.GetMasterNode()
167
  infocmd = utils.ShellQuoteArgs(["gnt-instance", "list", "--no-headers",
168
                                  "--units", "m", "-o", field, instance])
169
  return qa_utils.GetCommandOutput(master["primary"], infocmd).strip()
170

    
171

    
172
def _GetBoolInstanceField(instance, field):
173
  """Get the Boolean value of a field of an instance.
174

175
  @type instance: string
176
  @param instance: Instance name
177
  @type field: string
178
  @param field: Name of the field
179
  @rtype: bool
180

181
  """
182
  info_out = _GetInstanceField(instance, field)
183
  if info_out == "Y":
184
    return True
185
  elif info_out == "N":
186
    return False
187
  else:
188
    raise qa_error.Error("Field %s of instance %s has a non-Boolean value:"
189
                         " %s" % (field, instance, info_out))
190

    
191

    
192
def _GetNumInstanceField(instance, field):
193
  """Get a numeric value of a field of an instance.
194

195
  @type instance: string
196
  @param instance: Instance name
197
  @type field: string
198
  @param field: Name of the field
199
  @rtype: int or float
200

201
  """
202
  info_out = _GetInstanceField(instance, field)
203
  try:
204
    ret = int(info_out)
205
  except ValueError:
206
    try:
207
      ret = float(info_out)
208
    except ValueError:
209
      raise qa_error.Error("Field %s of instance %s has a non-numeric value:"
210
                           " %s" % (field, instance, info_out))
211
  return ret
212

    
213

    
214
def GetInstanceSpec(instance, spec):
215
  """Return the current spec for the given parameter.
216

217
  @type instance: string
218
  @param instance: Instance name
219
  @type spec: string
220
  @param spec: one of the supported parameters: "mem-size", "cpu-count",
221
      "disk-count", "disk-size", "nic-count"
222
  @rtype: tuple
223
  @return: (minspec, maxspec); minspec and maxspec can be different only for
224
      memory and disk size
225

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

    
244

    
245
def IsFailoverSupported(instance):
246
  templ = qa_config.GetInstanceTemplate(instance)
247
  return templ in constants.DTS_MIRRORED
248

    
249

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

    
254

    
255
def IsDiskReplacingSupported(instance):
256
  templ = qa_config.GetInstanceTemplate(instance)
257
  return templ == constants.DT_DRBD8
258

    
259

    
260
@InstanceCheck(None, INST_UP, RETURN_VALUE)
261
def TestInstanceAddWithPlainDisk(nodes):
262
  """gnt-instance add -t plain"""
263
  assert len(nodes) == 1
264
  return _DiskTest(nodes[0]["primary"], "plain")
265

    
266

    
267
@InstanceCheck(None, INST_UP, RETURN_VALUE)
268
def TestInstanceAddWithDrbdDisk(nodes):
269
  """gnt-instance add -t drbd"""
270
  assert len(nodes) == 2
271
  return _DiskTest(":".join(map(operator.itemgetter("primary"), nodes)),
272
                   "drbd")
273

    
274

    
275
@InstanceCheck(None, INST_DOWN, FIRST_ARG)
276
def TestInstanceRemove(instance):
277
  """gnt-instance remove"""
278
  AssertCommand(["gnt-instance", "remove", "-f", instance["name"]])
279

    
280
  qa_config.ReleaseInstance(instance)
281

    
282

    
283
@InstanceCheck(INST_DOWN, INST_UP, FIRST_ARG)
284
def TestInstanceStartup(instance):
285
  """gnt-instance startup"""
286
  AssertCommand(["gnt-instance", "startup", instance["name"]])
287

    
288

    
289
@InstanceCheck(INST_UP, INST_DOWN, FIRST_ARG)
290
def TestInstanceShutdown(instance):
291
  """gnt-instance shutdown"""
292
  AssertCommand(["gnt-instance", "shutdown", instance["name"]])
293

    
294

    
295
@InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
296
def TestInstanceReboot(instance):
297
  """gnt-instance reboot"""
298
  options = qa_config.get("options", {})
299
  reboot_types = options.get("reboot-types", constants.REBOOT_TYPES)
300
  name = instance["name"]
301
  for rtype in reboot_types:
302
    AssertCommand(["gnt-instance", "reboot", "--type=%s" % rtype, name])
303

    
304
  AssertCommand(["gnt-instance", "shutdown", name])
305
  qa_utils.RunInstanceCheck(instance, False)
306
  AssertCommand(["gnt-instance", "reboot", name])
307

    
308
  master = qa_config.GetMasterNode()
309
  cmd = ["gnt-instance", "list", "--no-headers", "-o", "status", name]
310
  result_output = qa_utils.GetCommandOutput(master["primary"],
311
                                            utils.ShellQuoteArgs(cmd))
312
  AssertEqual(result_output.strip(), constants.INSTST_RUNNING)
313

    
314

    
315
@InstanceCheck(INST_DOWN, INST_DOWN, FIRST_ARG)
316
def TestInstanceReinstall(instance):
317
  """gnt-instance reinstall"""
318
  AssertCommand(["gnt-instance", "reinstall", "-f", instance["name"]])
319

    
320
  # Test with non-existant OS definition
321
  AssertCommand(["gnt-instance", "reinstall", "-f",
322
                 "--os-type=NonExistantOsForQa",
323
                 instance["name"]],
324
                fail=True)
325

    
326

    
327
def _ReadSsconfInstanceList():
328
  """Reads ssconf_instance_list from the master node.
329

330
  """
331
  master = qa_config.GetMasterNode()
332

    
333
  cmd = ["cat", utils.PathJoin(pathutils.DATA_DIR,
334
                               "ssconf_%s" % constants.SS_INSTANCE_LIST)]
335

    
336
  return qa_utils.GetCommandOutput(master["primary"],
337
                                   utils.ShellQuoteArgs(cmd)).splitlines()
338

    
339

    
340
def _CheckSsconfInstanceList(instance):
341
  """Checks if a certain instance is in the ssconf instance list.
342

343
  @type instance: string
344
  @param instance: Instance name
345

346
  """
347
  AssertIn(qa_utils.ResolveInstanceName(instance),
348
           _ReadSsconfInstanceList())
349

    
350

    
351
@InstanceCheck(INST_DOWN, INST_DOWN, FIRST_ARG)
352
def TestInstanceRenameAndBack(rename_source, rename_target):
353
  """gnt-instance rename
354

355
  This must leave the instance with the original name, not the target
356
  name.
357

358
  """
359
  _CheckSsconfInstanceList(rename_source)
360

    
361
  # first do a rename to a different actual name, expecting it to fail
362
  qa_utils.AddToEtcHosts(["meeeeh-not-exists", rename_target])
363
  try:
364
    AssertCommand(["gnt-instance", "rename", rename_source, rename_target],
365
                  fail=True)
366
    _CheckSsconfInstanceList(rename_source)
367
  finally:
368
    qa_utils.RemoveFromEtcHosts(["meeeeh-not-exists", rename_target])
369

    
370
  # Check instance volume tags correctly updated
371
  # FIXME: this is LVM specific!
372
  info = _GetInstanceInfo(rename_source)
373
  tags_cmd = ("lvs -o tags --noheadings %s | grep " %
374
              (" ".join(info["volumes"]), ))
375

    
376
  # and now rename instance to rename_target...
377
  AssertCommand(["gnt-instance", "rename", rename_source, rename_target])
378
  _CheckSsconfInstanceList(rename_target)
379
  qa_utils.RunInstanceCheck(rename_source, False)
380
  qa_utils.RunInstanceCheck(rename_target, False)
381

    
382
  # NOTE: tags might not be the exactly as the instance name, due to
383
  # charset restrictions; hence the test might be flaky
384
  if rename_source != rename_target:
385
    for node in info["nodes"]:
386
      AssertCommand(tags_cmd + rename_source, node=node, fail=True)
387
      AssertCommand(tags_cmd + rename_target, node=node, fail=False)
388

    
389
  # and back
390
  AssertCommand(["gnt-instance", "rename", rename_target, rename_source])
391
  _CheckSsconfInstanceList(rename_source)
392
  qa_utils.RunInstanceCheck(rename_target, False)
393

    
394
  if rename_source != rename_target:
395
    for node in info["nodes"]:
396
      AssertCommand(tags_cmd + rename_source, node=node, fail=False)
397
      AssertCommand(tags_cmd + rename_target, node=node, fail=True)
398

    
399

    
400
@InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
401
def TestInstanceFailover(instance):
402
  """gnt-instance failover"""
403
  if not IsFailoverSupported(instance):
404
    print qa_utils.FormatInfo("Instance doesn't support failover, skipping"
405
                              " test")
406
    return
407

    
408
  cmd = ["gnt-instance", "failover", "--force", instance["name"]]
409

    
410
  # failover ...
411
  AssertCommand(cmd)
412
  qa_utils.RunInstanceCheck(instance, True)
413

    
414
  # ... and back
415
  AssertCommand(cmd)
416

    
417

    
418
@InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
419
def TestInstanceMigrate(instance, toggle_always_failover=True):
420
  """gnt-instance migrate"""
421
  if not IsMigrationSupported(instance):
422
    print qa_utils.FormatInfo("Instance doesn't support migration, skipping"
423
                              " test")
424
    return
425

    
426
  cmd = ["gnt-instance", "migrate", "--force", instance["name"]]
427
  af_par = constants.BE_ALWAYS_FAILOVER
428
  af_field = "be/" + constants.BE_ALWAYS_FAILOVER
429
  af_init_val = _GetBoolInstanceField(instance["name"], af_field)
430

    
431
  # migrate ...
432
  AssertCommand(cmd)
433
  # TODO: Verify the choice between failover and migration
434
  qa_utils.RunInstanceCheck(instance, True)
435

    
436
  # ... and back (possibly with always_failover toggled)
437
  if toggle_always_failover:
438
    AssertCommand(["gnt-instance", "modify", "-B",
439
                   ("%s=%s" % (af_par, not af_init_val)),
440
                   instance["name"]])
441
  AssertCommand(cmd)
442
  # TODO: Verify the choice between failover and migration
443
  qa_utils.RunInstanceCheck(instance, True)
444
  if toggle_always_failover:
445
    AssertCommand(["gnt-instance", "modify", "-B",
446
                   ("%s=%s" % (af_par, af_init_val)), instance["name"]])
447

    
448
  # TODO: Split into multiple tests
449
  AssertCommand(["gnt-instance", "shutdown", instance["name"]])
450
  qa_utils.RunInstanceCheck(instance, False)
451
  AssertCommand(cmd, fail=True)
452
  AssertCommand(["gnt-instance", "migrate", "--force", "--allow-failover",
453
                 instance["name"]])
454
  AssertCommand(["gnt-instance", "start", instance["name"]])
455
  AssertCommand(cmd)
456
  # @InstanceCheck enforces the check that the instance is running
457
  qa_utils.RunInstanceCheck(instance, True)
458

    
459
  AssertCommand(["gnt-instance", "modify", "-B",
460
                 ("%s=%s" %
461
                  (constants.BE_ALWAYS_FAILOVER, constants.VALUE_TRUE)),
462
                 instance["name"]])
463

    
464
  AssertCommand(cmd)
465
  qa_utils.RunInstanceCheck(instance, True)
466
  # TODO: Verify that a failover has been done instead of a migration
467

    
468
  # TODO: Verify whether the default value is restored here (not hardcoded)
469
  AssertCommand(["gnt-instance", "modify", "-B",
470
                 ("%s=%s" %
471
                  (constants.BE_ALWAYS_FAILOVER, constants.VALUE_FALSE)),
472
                 instance["name"]])
473

    
474
  AssertCommand(cmd)
475
  qa_utils.RunInstanceCheck(instance, True)
476

    
477

    
478
def TestInstanceInfo(instance):
479
  """gnt-instance info"""
480
  AssertCommand(["gnt-instance", "info", instance["name"]])
481

    
482

    
483
@InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
484
def TestInstanceModify(instance):
485
  """gnt-instance modify"""
486
  default_hv = qa_config.GetDefaultHypervisor()
487

    
488
  # Assume /sbin/init exists on all systems
489
  test_kernel = "/sbin/init"
490
  test_initrd = test_kernel
491

    
492
  orig_maxmem = qa_config.get(constants.BE_MAXMEM)
493
  orig_minmem = qa_config.get(constants.BE_MINMEM)
494
  #orig_bridge = qa_config.get("bridge", "xen-br0")
495

    
496
  args = [
497
    ["-B", "%s=128" % constants.BE_MINMEM],
498
    ["-B", "%s=128" % constants.BE_MAXMEM],
499
    ["-B", "%s=%s,%s=%s" % (constants.BE_MINMEM, orig_minmem,
500
                            constants.BE_MAXMEM, orig_maxmem)],
501
    ["-B", "%s=2" % constants.BE_VCPUS],
502
    ["-B", "%s=1" % constants.BE_VCPUS],
503
    ["-B", "%s=%s" % (constants.BE_VCPUS, constants.VALUE_DEFAULT)],
504
    ["-B", "%s=%s" % (constants.BE_ALWAYS_FAILOVER, constants.VALUE_TRUE)],
505
    ["-B", "%s=%s" % (constants.BE_ALWAYS_FAILOVER, constants.VALUE_DEFAULT)],
506

    
507
    ["-H", "%s=%s" % (constants.HV_KERNEL_PATH, test_kernel)],
508
    ["-H", "%s=%s" % (constants.HV_KERNEL_PATH, constants.VALUE_DEFAULT)],
509

    
510
    # TODO: bridge tests
511
    #["--bridge", "xen-br1"],
512
    #["--bridge", orig_bridge],
513
    ]
514

    
515
  if default_hv == constants.HT_XEN_PVM:
516
    args.extend([
517
      ["-H", "%s=%s" % (constants.HV_INITRD_PATH, test_initrd)],
518
      ["-H", "no_%s" % (constants.HV_INITRD_PATH, )],
519
      ["-H", "%s=%s" % (constants.HV_INITRD_PATH, constants.VALUE_DEFAULT)],
520
      ])
521
  elif default_hv == constants.HT_XEN_HVM:
522
    args.extend([
523
      ["-H", "%s=acn" % constants.HV_BOOT_ORDER],
524
      ["-H", "%s=%s" % (constants.HV_BOOT_ORDER, constants.VALUE_DEFAULT)],
525
      ])
526

    
527
  for alist in args:
528
    AssertCommand(["gnt-instance", "modify"] + alist + [instance["name"]])
529

    
530
  # check no-modify
531
  AssertCommand(["gnt-instance", "modify", instance["name"]], fail=True)
532

    
533
  # Marking offline while instance is running must fail...
534
  AssertCommand(["gnt-instance", "modify", "--offline", instance["name"]],
535
                 fail=True)
536

    
537
  # ...while making it online is ok, and should work
538
  AssertCommand(["gnt-instance", "modify", "--online", instance["name"]])
539

    
540

    
541
@InstanceCheck(INST_DOWN, INST_DOWN, FIRST_ARG)
542
def TestInstanceStoppedModify(instance):
543
  """gnt-instance modify (stopped instance)"""
544
  name = instance["name"]
545

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

    
549
  # Mark instance as offline
550
  AssertCommand(["gnt-instance", "modify", "--offline", name])
551

    
552
  # When the instance is offline shutdown should only work with --force,
553
  # while start should never work
554
  AssertCommand(["gnt-instance", "shutdown", name], fail=True)
555
  AssertCommand(["gnt-instance", "shutdown", "--force", name])
556
  AssertCommand(["gnt-instance", "start", name], fail=True)
557
  AssertCommand(["gnt-instance", "start", "--force", name], fail=True)
558

    
559
  # Also do offline to offline
560
  AssertCommand(["gnt-instance", "modify", "--offline", name])
561

    
562
  # And online again
563
  AssertCommand(["gnt-instance", "modify", "--online", name])
564

    
565

    
566
@InstanceCheck(INST_DOWN, INST_DOWN, FIRST_ARG)
567
def TestInstanceConvertDiskToPlain(instance, inodes):
568
  """gnt-instance modify -t"""
569
  name = instance["name"]
570
  template = qa_config.GetInstanceTemplate(instance)
571
  if template != "drbd":
572
    print qa_utils.FormatInfo("Unsupported template %s, skipping conversion"
573
                              " test" % template)
574
    return
575
  assert len(inodes) == 2
576
  AssertCommand(["gnt-instance", "modify", "-t", "plain", name])
577
  AssertCommand(["gnt-instance", "modify", "-t", "drbd",
578
                 "-n", inodes[1]["primary"], name])
579

    
580

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

    
606

    
607
def TestInstanceList():
608
  """gnt-instance list"""
609
  qa_utils.GenericQueryTest("gnt-instance", query.INSTANCE_FIELDS.keys())
610

    
611

    
612
def TestInstanceListFields():
613
  """gnt-instance list-fields"""
614
  qa_utils.GenericQueryFieldsTest("gnt-instance", query.INSTANCE_FIELDS.keys())
615

    
616

    
617
@InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
618
def TestInstanceConsole(instance):
619
  """gnt-instance console"""
620
  AssertCommand(["gnt-instance", "console", "--show-cmd", instance["name"]])
621

    
622

    
623
@InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
624
def TestReplaceDisks(instance, curr_nodes, other_nodes):
625
  """gnt-instance replace-disks"""
626
  def buildcmd(args):
627
    cmd = ["gnt-instance", "replace-disks"]
628
    cmd.extend(args)
629
    cmd.append(instance["name"])
630
    return cmd
631

    
632
  if not IsDiskReplacingSupported(instance):
633
    print qa_utils.FormatInfo("Instance doesn't support disk replacing,"
634
                              " skipping test")
635
    return
636

    
637
  # Currently all supported templates have one primary and one secondary node
638
  assert len(curr_nodes) == 2
639
  snode = curr_nodes[1]
640
  assert len(other_nodes) == 1
641
  othernode = other_nodes[0]
642

    
643
  options = qa_config.get("options", {})
644
  use_ialloc = options.get("use-iallocators", True)
645
  for data in [
646
    ["-p"],
647
    ["-s"],
648
    # A placeholder; the actual command choice depends on use_ialloc
649
    None,
650
    # Restore the original secondary
651
    ["--new-secondary=%s" % snode["primary"]],
652
    ]:
653
    if data is None:
654
      if use_ialloc:
655
        data = ["-I", constants.DEFAULT_IALLOCATOR_SHORTCUT]
656
      else:
657
        data = ["--new-secondary=%s" % othernode["primary"]]
658
    AssertCommand(buildcmd(data))
659

    
660
  AssertCommand(buildcmd(["-a"]))
661
  AssertCommand(["gnt-instance", "stop", instance["name"]])
662
  AssertCommand(buildcmd(["-a"]), fail=True)
663
  AssertCommand(["gnt-instance", "activate-disks", instance["name"]])
664
  AssertCommand(["gnt-instance", "activate-disks", "--wait-for-sync",
665
                 instance["name"]])
666
  AssertCommand(buildcmd(["-a"]))
667
  AssertCommand(["gnt-instance", "start", instance["name"]])
668

    
669

    
670
def _AssertRecreateDisks(cmdargs, instance, fail=False, check=True,
671
                         destroy=True):
672
  """Execute gnt-instance recreate-disks and check the result
673

674
  @param cmdargs: Arguments (instance name excluded)
675
  @param instance: Instance to operate on
676
  @param fail: True if the command is expected to fail
677
  @param check: If True and fail is False, check that the disks work
678
  @prama destroy: If True, destroy the old disks first
679

680
  """
681
  if destroy:
682
    _DestroyInstanceVolumes(instance)
683
  AssertCommand((["gnt-instance", "recreate-disks"] + cmdargs +
684
                 [instance["name"]]), fail)
685
  if not fail and check:
686
    # Quick check that the disks are there
687
    AssertCommand(["gnt-instance", "activate-disks", instance["name"]])
688
    AssertCommand(["gnt-instance", "activate-disks", "--wait-for-sync",
689
                   instance["name"]])
690
    AssertCommand(["gnt-instance", "deactivate-disks", instance["name"]])
691

    
692

    
693
@InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
694
def TestRecreateDisks(instance, inodes, othernodes):
695
  """gnt-instance recreate-disks
696

697
  @param instance: Instance to work on
698
  @param inodes: List of the current nodes of the instance
699
  @param othernodes: list/tuple of nodes where to temporarily recreate disks
700

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

    
731

    
732
@InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
733
def TestInstanceExport(instance, node):
734
  """gnt-backup export -n ..."""
735
  name = instance["name"]
736
  AssertCommand(["gnt-backup", "export", "-n", node["primary"], name])
737
  return qa_utils.ResolveInstanceName(name)
738

    
739

    
740
@InstanceCheck(None, INST_DOWN, FIRST_ARG)
741
def TestInstanceExportWithRemove(instance, node):
742
  """gnt-backup export --remove-instance"""
743
  AssertCommand(["gnt-backup", "export", "-n", node["primary"],
744
                 "--remove-instance", instance["name"]])
745
  qa_config.ReleaseInstance(instance)
746

    
747

    
748
@InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
749
def TestInstanceExportNoTarget(instance):
750
  """gnt-backup export (without target node, should fail)"""
751
  AssertCommand(["gnt-backup", "export", instance["name"]], fail=True)
752

    
753

    
754
@InstanceCheck(None, INST_DOWN, FIRST_ARG)
755
def TestInstanceImport(newinst, node, expnode, name):
756
  """gnt-backup import"""
757
  templ = constants.DT_PLAIN
758
  cmd = (["gnt-backup", "import",
759
          "--disk-template=%s" % templ,
760
          "--no-ip-check",
761
          "--src-node=%s" % expnode["primary"],
762
          "--src-dir=%s/%s" % (pathutils.EXPORT_DIR, name),
763
          "--node=%s" % node["primary"]] +
764
         _GetGenericAddParameters(newinst, force_mac=constants.VALUE_GENERATE))
765
  cmd.append(newinst["name"])
766
  AssertCommand(cmd)
767
  qa_config.SetInstanceTemplate(newinst, templ)
768

    
769

    
770
def TestBackupList(expnode):
771
  """gnt-backup list"""
772
  AssertCommand(["gnt-backup", "list", "--node=%s" % expnode["primary"]])
773

    
774
  qa_utils.GenericQueryTest("gnt-backup", query.EXPORT_FIELDS.keys(),
775
                            namefield=None, test_unknown=False)
776

    
777

    
778
def TestBackupListFields():
779
  """gnt-backup list-fields"""
780
  qa_utils.GenericQueryFieldsTest("gnt-backup", query.EXPORT_FIELDS.keys())
781

    
782

    
783
def TestRemoveInstanceOfflineNode(instance, snode, set_offline, set_online):
784
  """gtn-instance remove with an off-line node
785

786
  @param instance: instance
787
  @param snode: secondary node, to be set offline
788
  @param set_offline: function to call to set the node off-line
789
  @param set_online: function to call to set the node on-line
790

791
  """
792
  info = _GetInstanceInfo(instance["name"])
793
  set_offline(snode)
794
  try:
795
    TestInstanceRemove(instance)
796
  finally:
797
    set_online(snode)
798
  # Clean up the disks on the offline node
799
  for minor in info["drbd-minors"][snode["primary"]]:
800
    AssertCommand(["drbdsetup", str(minor), "down"], node=snode)
801
  AssertCommand(["lvremove", "-f"] + info["volumes"], node=snode)