Statistics
| Branch: | Tag: | Revision:

root / qa / qa_instance.py @ 33c730a2

History | View | Annotate | Download (27 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, disk_template, 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

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

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

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

    
66
  return params
67

    
68

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

    
79
    AssertCommand(cmd, fail=fail)
80

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

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

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

    
95

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

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

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

    
146
  assert nodes
147
  assert len(nodes) < 2 or vols
148

    
149
  return {
150
    "nodes": nodes,
151
    "volumes": vols,
152
    "drbd-minors": drbd_min,
153
    }
154

    
155

    
156
def _DestroyInstanceVolumes(instance):
157
  """Remove all the LVM volumes of an instance.
158

159
  This is used to simulate HW errors (dead nodes, broken disks...); the
160
  configuration of the instance is not affected.
161
  @type instance: dictionary
162
  @param instance: the instance
163

164
  """
165
  info = _GetInstanceInfo(instance.name)
166
  vols = info["volumes"]
167
  for node in info["nodes"]:
168
    AssertCommand(["lvremove", "-f"] + vols, node=node)
169

    
170

    
171
def _GetInstanceField(instance, field):
172
  """Get the value of a field of an instance.
173

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

180
  """
181
  master = qa_config.GetMasterNode()
182
  infocmd = utils.ShellQuoteArgs(["gnt-instance", "list", "--no-headers",
183
                                  "--units", "m", "-o", field, instance])
184
  return qa_utils.GetCommandOutput(master.primary, infocmd).strip()
185

    
186

    
187
def _GetBoolInstanceField(instance, field):
188
  """Get the Boolean value of a field of an instance.
189

190
  @type instance: string
191
  @param instance: Instance name
192
  @type field: string
193
  @param field: Name of the field
194
  @rtype: bool
195

196
  """
197
  info_out = _GetInstanceField(instance, field)
198
  if info_out == "Y":
199
    return True
200
  elif info_out == "N":
201
    return False
202
  else:
203
    raise qa_error.Error("Field %s of instance %s has a non-Boolean value:"
204
                         " %s" % (field, instance, info_out))
205

    
206

    
207
def _GetNumInstanceField(instance, field):
208
  """Get a numeric value of a field of an instance.
209

210
  @type instance: string
211
  @param instance: Instance name
212
  @type field: string
213
  @param field: Name of the field
214
  @rtype: int or float
215

216
  """
217
  info_out = _GetInstanceField(instance, field)
218
  try:
219
    ret = int(info_out)
220
  except ValueError:
221
    try:
222
      ret = float(info_out)
223
    except ValueError:
224
      raise qa_error.Error("Field %s of instance %s has a non-numeric value:"
225
                           " %s" % (field, instance, info_out))
226
  return ret
227

    
228

    
229
def GetInstanceSpec(instance, spec):
230
  """Return the current spec for the given parameter.
231

232
  @type instance: string
233
  @param instance: Instance name
234
  @type spec: string
235
  @param spec: one of the supported parameters: "mem-size", "cpu-count",
236
      "disk-count", "disk-size", "nic-count"
237
  @rtype: tuple
238
  @return: (minspec, maxspec); minspec and maxspec can be different only for
239
      memory and disk size
240

241
  """
242
  specmap = {
243
    "mem-size": ["be/minmem", "be/maxmem"],
244
    "cpu-count": ["vcpus"],
245
    "disk-count": ["disk.count"],
246
    "disk-size": ["disk.size/ "],
247
    "nic-count": ["nic.count"],
248
    }
249
  # For disks, first we need the number of disks
250
  if spec == "disk-size":
251
    (numdisk, _) = GetInstanceSpec(instance, "disk-count")
252
    fields = ["disk.size/%s" % k for k in range(0, numdisk)]
253
  else:
254
    assert spec in specmap, "%s not in %s" % (spec, specmap)
255
    fields = specmap[spec]
256
  values = [_GetNumInstanceField(instance, f) for f in fields]
257
  return (min(values), max(values))
258

    
259

    
260
def IsFailoverSupported(instance):
261
  return instance.disk_template in constants.DTS_MIRRORED
262

    
263

    
264
def IsMigrationSupported(instance):
265
  return instance.disk_template in constants.DTS_MIRRORED
266

    
267

    
268
def IsDiskReplacingSupported(instance):
269
  return instance.disk_template == constants.DT_DRBD8
270

    
271

    
272
def TestInstanceAddWithPlainDisk(nodes, fail=False):
273
  """gnt-instance add -t plain"""
274
  assert len(nodes) == 1
275
  instance = _DiskTest(nodes[0].primary, constants.DT_PLAIN, fail=fail)
276
  if not fail:
277
    qa_utils.RunInstanceCheck(instance, True)
278
  return instance
279

    
280

    
281
@InstanceCheck(None, INST_UP, RETURN_VALUE)
282
def TestInstanceAddWithDrbdDisk(nodes):
283
  """gnt-instance add -t drbd"""
284
  assert len(nodes) == 2
285
  return _DiskTest(":".join(map(operator.attrgetter("primary"), nodes)),
286
                   constants.DT_DRBD8)
287

    
288

    
289
@InstanceCheck(None, INST_UP, RETURN_VALUE)
290
def TestInstanceAddDiskless(nodes):
291
  """gnt-instance add -t diskless"""
292
  assert len(nodes) == 1
293
  return _DiskTest(nodes[0].primary, constants.DT_DISKLESS)
294

    
295

    
296
@InstanceCheck(None, INST_DOWN, FIRST_ARG)
297
def TestInstanceRemove(instance):
298
  """gnt-instance remove"""
299
  AssertCommand(["gnt-instance", "remove", "-f", instance.name])
300

    
301

    
302
@InstanceCheck(INST_DOWN, INST_UP, FIRST_ARG)
303
def TestInstanceStartup(instance):
304
  """gnt-instance startup"""
305
  AssertCommand(["gnt-instance", "startup", instance.name])
306

    
307

    
308
@InstanceCheck(INST_UP, INST_DOWN, FIRST_ARG)
309
def TestInstanceShutdown(instance):
310
  """gnt-instance shutdown"""
311
  AssertCommand(["gnt-instance", "shutdown", instance.name])
312

    
313

    
314
@InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
315
def TestInstanceReboot(instance):
316
  """gnt-instance reboot"""
317
  options = qa_config.get("options", {})
318
  reboot_types = options.get("reboot-types", constants.REBOOT_TYPES)
319
  name = instance.name
320
  for rtype in reboot_types:
321
    AssertCommand(["gnt-instance", "reboot", "--type=%s" % rtype, name])
322

    
323
  AssertCommand(["gnt-instance", "shutdown", name])
324
  qa_utils.RunInstanceCheck(instance, False)
325
  AssertCommand(["gnt-instance", "reboot", name])
326

    
327
  master = qa_config.GetMasterNode()
328
  cmd = ["gnt-instance", "list", "--no-headers", "-o", "status", name]
329
  result_output = qa_utils.GetCommandOutput(master.primary,
330
                                            utils.ShellQuoteArgs(cmd))
331
  AssertEqual(result_output.strip(), constants.INSTST_RUNNING)
332

    
333

    
334
@InstanceCheck(INST_DOWN, INST_DOWN, FIRST_ARG)
335
def TestInstanceReinstall(instance):
336
  """gnt-instance reinstall"""
337
  if instance.disk_template == constants.DT_DISKLESS:
338
    print qa_utils.FormatInfo("Test not supported for diskless instances")
339
    return
340

    
341
  AssertCommand(["gnt-instance", "reinstall", "-f", instance.name])
342

    
343
  # Test with non-existant OS definition
344
  AssertCommand(["gnt-instance", "reinstall", "-f",
345
                 "--os-type=NonExistantOsForQa",
346
                 instance.name],
347
                fail=True)
348

    
349

    
350
def _ReadSsconfInstanceList():
351
  """Reads ssconf_instance_list from the master node.
352

353
  """
354
  master = qa_config.GetMasterNode()
355

    
356
  ssconf_path = utils.PathJoin(pathutils.DATA_DIR,
357
                               "ssconf_%s" % constants.SS_INSTANCE_LIST)
358

    
359
  cmd = ["cat", qa_utils.MakeNodePath(master, ssconf_path)]
360

    
361
  return qa_utils.GetCommandOutput(master.primary,
362
                                   utils.ShellQuoteArgs(cmd)).splitlines()
363

    
364

    
365
def _CheckSsconfInstanceList(instance):
366
  """Checks if a certain instance is in the ssconf instance list.
367

368
  @type instance: string
369
  @param instance: Instance name
370

371
  """
372
  AssertIn(qa_utils.ResolveInstanceName(instance),
373
           _ReadSsconfInstanceList())
374

    
375

    
376
@InstanceCheck(INST_DOWN, INST_DOWN, FIRST_ARG)
377
def TestInstanceRenameAndBack(rename_source, rename_target):
378
  """gnt-instance rename
379

380
  This must leave the instance with the original name, not the target
381
  name.
382

383
  """
384
  _CheckSsconfInstanceList(rename_source)
385

    
386
  # first do a rename to a different actual name, expecting it to fail
387
  qa_utils.AddToEtcHosts(["meeeeh-not-exists", rename_target])
388
  try:
389
    AssertCommand(["gnt-instance", "rename", rename_source, rename_target],
390
                  fail=True)
391
    _CheckSsconfInstanceList(rename_source)
392
  finally:
393
    qa_utils.RemoveFromEtcHosts(["meeeeh-not-exists", rename_target])
394

    
395
  # Check instance volume tags correctly updated
396
  # FIXME: this is LVM specific!
397
  info = _GetInstanceInfo(rename_source)
398
  tags_cmd = ("lvs -o tags --noheadings %s | grep " %
399
              (" ".join(info["volumes"]), ))
400

    
401
  # and now rename instance to rename_target...
402
  AssertCommand(["gnt-instance", "rename", rename_source, rename_target])
403
  _CheckSsconfInstanceList(rename_target)
404
  qa_utils.RunInstanceCheck(rename_source, False)
405
  qa_utils.RunInstanceCheck(rename_target, False)
406

    
407
  # NOTE: tags might not be the exactly as the instance name, due to
408
  # charset restrictions; hence the test might be flaky
409
  if rename_source != rename_target:
410
    for node in info["nodes"]:
411
      AssertCommand(tags_cmd + rename_source, node=node, fail=True)
412
      AssertCommand(tags_cmd + rename_target, node=node, fail=False)
413

    
414
  # and back
415
  AssertCommand(["gnt-instance", "rename", rename_target, rename_source])
416
  _CheckSsconfInstanceList(rename_source)
417
  qa_utils.RunInstanceCheck(rename_target, False)
418

    
419
  if rename_source != rename_target:
420
    for node in info["nodes"]:
421
      AssertCommand(tags_cmd + rename_source, node=node, fail=False)
422
      AssertCommand(tags_cmd + rename_target, node=node, fail=True)
423

    
424

    
425
@InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
426
def TestInstanceFailover(instance):
427
  """gnt-instance failover"""
428
  if not IsFailoverSupported(instance):
429
    print qa_utils.FormatInfo("Instance doesn't support failover, skipping"
430
                              " test")
431
    return
432

    
433
  cmd = ["gnt-instance", "failover", "--force", instance.name]
434

    
435
  # failover ...
436
  AssertCommand(cmd)
437
  qa_utils.RunInstanceCheck(instance, True)
438

    
439
  # ... and back
440
  AssertCommand(cmd)
441

    
442

    
443
@InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
444
def TestInstanceMigrate(instance, toggle_always_failover=True):
445
  """gnt-instance migrate"""
446
  if not IsMigrationSupported(instance):
447
    print qa_utils.FormatInfo("Instance doesn't support migration, skipping"
448
                              " test")
449
    return
450

    
451
  cmd = ["gnt-instance", "migrate", "--force", instance.name]
452
  af_par = constants.BE_ALWAYS_FAILOVER
453
  af_field = "be/" + constants.BE_ALWAYS_FAILOVER
454
  af_init_val = _GetBoolInstanceField(instance.name, af_field)
455

    
456
  # migrate ...
457
  AssertCommand(cmd)
458
  # TODO: Verify the choice between failover and migration
459
  qa_utils.RunInstanceCheck(instance, True)
460

    
461
  # ... and back (possibly with always_failover toggled)
462
  if toggle_always_failover:
463
    AssertCommand(["gnt-instance", "modify", "-B",
464
                   ("%s=%s" % (af_par, not af_init_val)),
465
                   instance.name])
466
  AssertCommand(cmd)
467
  # TODO: Verify the choice between failover and migration
468
  qa_utils.RunInstanceCheck(instance, True)
469
  if toggle_always_failover:
470
    AssertCommand(["gnt-instance", "modify", "-B",
471
                   ("%s=%s" % (af_par, af_init_val)), instance.name])
472

    
473
  # TODO: Split into multiple tests
474
  AssertCommand(["gnt-instance", "shutdown", instance.name])
475
  qa_utils.RunInstanceCheck(instance, False)
476
  AssertCommand(cmd, fail=True)
477
  AssertCommand(["gnt-instance", "migrate", "--force", "--allow-failover",
478
                 instance.name])
479
  AssertCommand(["gnt-instance", "start", instance.name])
480
  AssertCommand(cmd)
481
  # @InstanceCheck enforces the check that the instance is running
482
  qa_utils.RunInstanceCheck(instance, True)
483

    
484
  AssertCommand(["gnt-instance", "modify", "-B",
485
                 ("%s=%s" %
486
                  (constants.BE_ALWAYS_FAILOVER, constants.VALUE_TRUE)),
487
                 instance.name])
488

    
489
  AssertCommand(cmd)
490
  qa_utils.RunInstanceCheck(instance, True)
491
  # TODO: Verify that a failover has been done instead of a migration
492

    
493
  # TODO: Verify whether the default value is restored here (not hardcoded)
494
  AssertCommand(["gnt-instance", "modify", "-B",
495
                 ("%s=%s" %
496
                  (constants.BE_ALWAYS_FAILOVER, constants.VALUE_FALSE)),
497
                 instance.name])
498

    
499
  AssertCommand(cmd)
500
  qa_utils.RunInstanceCheck(instance, True)
501

    
502

    
503
def TestInstanceInfo(instance):
504
  """gnt-instance info"""
505
  AssertCommand(["gnt-instance", "info", instance.name])
506

    
507

    
508
@InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
509
def TestInstanceModify(instance):
510
  """gnt-instance modify"""
511
  default_hv = qa_config.GetDefaultHypervisor()
512

    
513
  # Assume /sbin/init exists on all systems
514
  test_kernel = "/sbin/init"
515
  test_initrd = test_kernel
516

    
517
  orig_maxmem = qa_config.get(constants.BE_MAXMEM)
518
  orig_minmem = qa_config.get(constants.BE_MINMEM)
519
  #orig_bridge = qa_config.get("bridge", "xen-br0")
520

    
521
  args = [
522
    ["-B", "%s=128" % constants.BE_MINMEM],
523
    ["-B", "%s=128" % constants.BE_MAXMEM],
524
    ["-B", "%s=%s,%s=%s" % (constants.BE_MINMEM, orig_minmem,
525
                            constants.BE_MAXMEM, orig_maxmem)],
526
    ["-B", "%s=2" % constants.BE_VCPUS],
527
    ["-B", "%s=1" % constants.BE_VCPUS],
528
    ["-B", "%s=%s" % (constants.BE_VCPUS, constants.VALUE_DEFAULT)],
529
    ["-B", "%s=%s" % (constants.BE_ALWAYS_FAILOVER, constants.VALUE_TRUE)],
530
    ["-B", "%s=%s" % (constants.BE_ALWAYS_FAILOVER, constants.VALUE_DEFAULT)],
531

    
532
    ["-H", "%s=%s" % (constants.HV_KERNEL_PATH, test_kernel)],
533
    ["-H", "%s=%s" % (constants.HV_KERNEL_PATH, constants.VALUE_DEFAULT)],
534

    
535
    # TODO: bridge tests
536
    #["--bridge", "xen-br1"],
537
    #["--bridge", orig_bridge],
538
    ]
539

    
540
  if default_hv == constants.HT_XEN_PVM:
541
    args.extend([
542
      ["-H", "%s=%s" % (constants.HV_INITRD_PATH, test_initrd)],
543
      ["-H", "no_%s" % (constants.HV_INITRD_PATH, )],
544
      ["-H", "%s=%s" % (constants.HV_INITRD_PATH, constants.VALUE_DEFAULT)],
545
      ])
546
  elif default_hv == constants.HT_XEN_HVM:
547
    args.extend([
548
      ["-H", "%s=acn" % constants.HV_BOOT_ORDER],
549
      ["-H", "%s=%s" % (constants.HV_BOOT_ORDER, constants.VALUE_DEFAULT)],
550
      ])
551

    
552
  for alist in args:
553
    AssertCommand(["gnt-instance", "modify"] + alist + [instance.name])
554

    
555
  # check no-modify
556
  AssertCommand(["gnt-instance", "modify", instance.name], fail=True)
557

    
558
  # Marking offline while instance is running must fail...
559
  AssertCommand(["gnt-instance", "modify", "--offline", instance.name],
560
                 fail=True)
561

    
562
  # ...while making it online is ok, and should work
563
  AssertCommand(["gnt-instance", "modify", "--online", instance.name])
564

    
565

    
566
@InstanceCheck(INST_DOWN, INST_DOWN, FIRST_ARG)
567
def TestInstanceStoppedModify(instance):
568
  """gnt-instance modify (stopped instance)"""
569
  name = instance.name
570

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

    
574
  # Mark instance as offline
575
  AssertCommand(["gnt-instance", "modify", "--offline", name])
576

    
577
  # When the instance is offline shutdown should only work with --force,
578
  # while start should never work
579
  AssertCommand(["gnt-instance", "shutdown", name], fail=True)
580
  AssertCommand(["gnt-instance", "shutdown", "--force", name])
581
  AssertCommand(["gnt-instance", "start", name], fail=True)
582
  AssertCommand(["gnt-instance", "start", "--force", name], fail=True)
583

    
584
  # Also do offline to offline
585
  AssertCommand(["gnt-instance", "modify", "--offline", name])
586

    
587
  # And online again
588
  AssertCommand(["gnt-instance", "modify", "--online", name])
589

    
590

    
591
@InstanceCheck(INST_DOWN, INST_DOWN, FIRST_ARG)
592
def TestInstanceConvertDiskToPlain(instance, inodes):
593
  """gnt-instance modify -t"""
594
  name = instance.name
595

    
596
  template = instance.disk_template
597
  if template != constants.DT_DRBD8:
598
    print qa_utils.FormatInfo("Unsupported template %s, skipping conversion"
599
                              " test" % template)
600
    return
601

    
602
  assert len(inodes) == 2
603
  AssertCommand(["gnt-instance", "modify", "-t", constants.DT_PLAIN, name])
604
  AssertCommand(["gnt-instance", "modify", "-t", constants.DT_DRBD8,
605
                 "-n", inodes[1].primary, name])
606

    
607

    
608
@InstanceCheck(INST_DOWN, INST_DOWN, FIRST_ARG)
609
def TestInstanceGrowDisk(instance):
610
  """gnt-instance grow-disk"""
611
  if qa_config.GetExclusiveStorage():
612
    print qa_utils.FormatInfo("Test not supported with exclusive_storage")
613
    return
614

    
615
  if instance.disk_template == constants.DT_DISKLESS:
616
    print qa_utils.FormatInfo("Test not supported for diskless instances")
617
    return
618

    
619
  name = instance.name
620
  all_size = qa_config.get("disk")
621
  all_grow = qa_config.get("disk-growth")
622

    
623
  if not all_grow:
624
    # missing disk sizes but instance grow disk has been enabled,
625
    # let's set fixed/nomimal growth
626
    all_grow = ["128M" for _ in all_size]
627

    
628
  for idx, (size, grow) in enumerate(zip(all_size, all_grow)):
629
    # succeed in grow by amount
630
    AssertCommand(["gnt-instance", "grow-disk", name, str(idx), grow])
631
    # fail in grow to the old size
632
    AssertCommand(["gnt-instance", "grow-disk", "--absolute", name, str(idx),
633
                   size], fail=True)
634
    # succeed to grow to old size + 2 * growth
635
    int_size = utils.ParseUnit(size)
636
    int_grow = utils.ParseUnit(grow)
637
    AssertCommand(["gnt-instance", "grow-disk", "--absolute", name, str(idx),
638
                   str(int_size + 2 * int_grow)])
639

    
640

    
641
def TestInstanceList():
642
  """gnt-instance list"""
643
  qa_utils.GenericQueryTest("gnt-instance", query.INSTANCE_FIELDS.keys())
644

    
645

    
646
def TestInstanceListFields():
647
  """gnt-instance list-fields"""
648
  qa_utils.GenericQueryFieldsTest("gnt-instance", query.INSTANCE_FIELDS.keys())
649

    
650

    
651
@InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
652
def TestInstanceConsole(instance):
653
  """gnt-instance console"""
654
  AssertCommand(["gnt-instance", "console", "--show-cmd", instance.name])
655

    
656

    
657
@InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
658
def TestReplaceDisks(instance, curr_nodes, other_nodes):
659
  """gnt-instance replace-disks"""
660
  def buildcmd(args):
661
    cmd = ["gnt-instance", "replace-disks"]
662
    cmd.extend(args)
663
    cmd.append(instance.name)
664
    return cmd
665

    
666
  if not IsDiskReplacingSupported(instance):
667
    print qa_utils.FormatInfo("Instance doesn't support disk replacing,"
668
                              " skipping test")
669
    return
670

    
671
  # Currently all supported templates have one primary and one secondary node
672
  assert len(curr_nodes) == 2
673
  snode = curr_nodes[1]
674
  assert len(other_nodes) == 1
675
  othernode = other_nodes[0]
676

    
677
  options = qa_config.get("options", {})
678
  use_ialloc = options.get("use-iallocators", True)
679
  for data in [
680
    ["-p"],
681
    ["-s"],
682
    # A placeholder; the actual command choice depends on use_ialloc
683
    None,
684
    # Restore the original secondary
685
    ["--new-secondary=%s" % snode.primary],
686
    ]:
687
    if data is None:
688
      if use_ialloc:
689
        data = ["-I", constants.DEFAULT_IALLOCATOR_SHORTCUT]
690
      else:
691
        data = ["--new-secondary=%s" % othernode.primary]
692
    AssertCommand(buildcmd(data))
693

    
694
  AssertCommand(buildcmd(["-a"]))
695
  AssertCommand(["gnt-instance", "stop", instance.name])
696
  AssertCommand(buildcmd(["-a"]), fail=True)
697
  AssertCommand(["gnt-instance", "activate-disks", instance.name])
698
  AssertCommand(["gnt-instance", "activate-disks", "--wait-for-sync",
699
                 instance.name])
700
  AssertCommand(buildcmd(["-a"]))
701
  AssertCommand(["gnt-instance", "start", instance.name])
702

    
703

    
704
def _AssertRecreateDisks(cmdargs, instance, fail=False, check=True,
705
                         destroy=True):
706
  """Execute gnt-instance recreate-disks and check the result
707

708
  @param cmdargs: Arguments (instance name excluded)
709
  @param instance: Instance to operate on
710
  @param fail: True if the command is expected to fail
711
  @param check: If True and fail is False, check that the disks work
712
  @prama destroy: If True, destroy the old disks first
713

714
  """
715
  if destroy:
716
    _DestroyInstanceVolumes(instance)
717
  AssertCommand((["gnt-instance", "recreate-disks"] + cmdargs +
718
                 [instance.name]), fail)
719
  if not fail and check:
720
    # Quick check that the disks are there
721
    AssertCommand(["gnt-instance", "activate-disks", instance.name])
722
    AssertCommand(["gnt-instance", "activate-disks", "--wait-for-sync",
723
                   instance.name])
724
    AssertCommand(["gnt-instance", "deactivate-disks", instance.name])
725

    
726

    
727
@InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
728
def TestRecreateDisks(instance, inodes, othernodes):
729
  """gnt-instance recreate-disks
730

731
  @param instance: Instance to work on
732
  @param inodes: List of the current nodes of the instance
733
  @param othernodes: list/tuple of nodes where to temporarily recreate disks
734

735
  """
736
  options = qa_config.get("options", {})
737
  use_ialloc = options.get("use-iallocators", True)
738
  other_seq = ":".join([n.primary for n in othernodes])
739
  orig_seq = ":".join([n.primary for n in inodes])
740
  # These fail because the instance is running
741
  _AssertRecreateDisks(["-n", other_seq], instance, fail=True, destroy=False)
742
  if use_ialloc:
743
    _AssertRecreateDisks(["-I", "hail"], instance, fail=True, destroy=False)
744
  else:
745
    _AssertRecreateDisks(["-n", other_seq], instance, fail=True, destroy=False)
746
  AssertCommand(["gnt-instance", "stop", instance.name])
747
  # Disks exist: this should fail
748
  _AssertRecreateDisks([], instance, fail=True, destroy=False)
749
  # Recreate disks in place
750
  _AssertRecreateDisks([], instance)
751
  # Move disks away
752
  if use_ialloc:
753
    _AssertRecreateDisks(["-I", "hail"], instance)
754
    # Move disks somewhere else
755
    _AssertRecreateDisks(["-I", constants.DEFAULT_IALLOCATOR_SHORTCUT],
756
                         instance)
757
  else:
758
    _AssertRecreateDisks(["-n", other_seq], instance)
759
  # Move disks back
760
  _AssertRecreateDisks(["-n", orig_seq], instance, check=False)
761
  # This and InstanceCheck decoration check that the disks are working
762
  AssertCommand(["gnt-instance", "reinstall", "-f", instance.name])
763
  AssertCommand(["gnt-instance", "start", instance.name])
764

    
765

    
766
@InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
767
def TestInstanceExport(instance, node):
768
  """gnt-backup export -n ..."""
769
  name = instance.name
770
  AssertCommand(["gnt-backup", "export", "-n", node.primary, name])
771
  return qa_utils.ResolveInstanceName(name)
772

    
773

    
774
@InstanceCheck(None, INST_DOWN, FIRST_ARG)
775
def TestInstanceExportWithRemove(instance, node):
776
  """gnt-backup export --remove-instance"""
777
  AssertCommand(["gnt-backup", "export", "-n", node.primary,
778
                 "--remove-instance", instance.name])
779

    
780

    
781
@InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
782
def TestInstanceExportNoTarget(instance):
783
  """gnt-backup export (without target node, should fail)"""
784
  AssertCommand(["gnt-backup", "export", instance.name], fail=True)
785

    
786

    
787
@InstanceCheck(None, INST_DOWN, FIRST_ARG)
788
def TestInstanceImport(newinst, node, expnode, name):
789
  """gnt-backup import"""
790
  templ = constants.DT_PLAIN
791
  cmd = (["gnt-backup", "import",
792
          "--disk-template=%s" % templ,
793
          "--no-ip-check",
794
          "--src-node=%s" % expnode.primary,
795
          "--src-dir=%s/%s" % (pathutils.EXPORT_DIR, name),
796
          "--node=%s" % node.primary] +
797
         _GetGenericAddParameters(newinst, templ,
798
                                  force_mac=constants.VALUE_GENERATE))
799
  cmd.append(newinst.name)
800
  AssertCommand(cmd)
801
  newinst.SetDiskTemplate(templ)
802

    
803

    
804
def TestBackupList(expnode):
805
  """gnt-backup list"""
806
  AssertCommand(["gnt-backup", "list", "--node=%s" % expnode.primary])
807

    
808
  qa_utils.GenericQueryTest("gnt-backup", query.EXPORT_FIELDS.keys(),
809
                            namefield=None, test_unknown=False)
810

    
811

    
812
def TestBackupListFields():
813
  """gnt-backup list-fields"""
814
  qa_utils.GenericQueryFieldsTest("gnt-backup", query.EXPORT_FIELDS.keys())
815

    
816

    
817
def TestRemoveInstanceOfflineNode(instance, snode, set_offline, set_online):
818
  """gtn-instance remove with an off-line node
819

820
  @param instance: instance
821
  @param snode: secondary node, to be set offline
822
  @param set_offline: function to call to set the node off-line
823
  @param set_online: function to call to set the node on-line
824

825
  """
826
  info = _GetInstanceInfo(instance.name)
827
  set_offline(snode)
828
  try:
829
    TestInstanceRemove(instance)
830
  finally:
831
    set_online(snode)
832
  # Clean up the disks on the offline node
833
  for minor in info["drbd-minors"][snode.primary]:
834
    AssertCommand(["drbdsetup", str(minor), "down"], node=snode)
835
  AssertCommand(["lvremove", "-f"] + info["volumes"], node=snode)