Statistics
| Branch: | Tag: | Revision:

root / qa / qa_instance.py @ 6970c89c

History | View | Annotate | Download (27.3 kB)

1
#
2
#
3

    
4
# Copyright (C) 2007, 2011, 2012, 2013 Google Inc.
5
#
6
# This program is free software; you can redistribute it and/or modify
7
# it under the terms of the GNU General Public License as published by
8
# the Free Software Foundation; either version 2 of the License, or
9
# (at your option) any later version.
10
#
11
# This program is distributed in the hope that it will be useful, but
12
# WITHOUT ANY WARRANTY; without even the implied warranty of
13
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14
# General Public License for more details.
15
#
16
# You should have received a copy of the GNU General Public License
17
# along with this program; if not, write to the Free Software
18
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19
# 02110-1301, USA.
20

    
21

    
22
"""Instance related QA tests.
23

24
"""
25

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

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

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

    
150
  assert nodes
151
  assert len(nodes) < 2 or vols
152
  return {
153
    "nodes": nodes,
154
    "volumes": vols,
155
    "drbd-minors": drbd_min,
156
    }
157

    
158

    
159
def _DestroyInstanceVolumes(instance):
160
  """Remove all the LVM volumes of an instance.
161

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

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

    
173

    
174
def _GetInstanceField(instance, field):
175
  """Get the value of a field of an instance.
176

177
  @type instance: string
178
  @param instance: Instance name
179
  @type field: string
180
  @param field: Name of the field
181
  @rtype: string
182

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

    
189

    
190
def _GetBoolInstanceField(instance, field):
191
  """Get the Boolean value of a field of an instance.
192

193
  @type instance: string
194
  @param instance: Instance name
195
  @type field: string
196
  @param field: Name of the field
197
  @rtype: bool
198

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

    
209

    
210
def _GetNumInstanceField(instance, field):
211
  """Get a numeric value of a field of an instance.
212

213
  @type instance: string
214
  @param instance: Instance name
215
  @type field: string
216
  @param field: Name of the field
217
  @rtype: int or float
218

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

    
231

    
232
def GetInstanceSpec(instance, spec):
233
  """Return the current spec for the given parameter.
234

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

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

    
262

    
263
def IsFailoverSupported(instance):
264
  return instance.disk_template in constants.DTS_MIRRORED
265

    
266

    
267
def IsMigrationSupported(instance):
268
  return instance.disk_template in constants.DTS_MIRRORED
269

    
270

    
271
def IsDiskReplacingSupported(instance):
272
  return instance.disk_template == constants.DT_DRBD8
273

    
274

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

    
283

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

    
291

    
292
@InstanceCheck(None, INST_UP, RETURN_VALUE)
293
def TestInstanceAddFile(nodes):
294
  """gnt-instance add -t file"""
295
  assert len(nodes) == 1
296
  return _DiskTest(nodes[0].primary, constants.DT_FILE)
297

    
298

    
299
@InstanceCheck(None, INST_UP, RETURN_VALUE)
300
def TestInstanceAddDiskless(nodes):
301
  """gnt-instance add -t diskless"""
302
  assert len(nodes) == 1
303
  return _DiskTest(nodes[0].primary, constants.DT_DISKLESS)
304

    
305

    
306
@InstanceCheck(None, INST_DOWN, FIRST_ARG)
307
def TestInstanceRemove(instance):
308
  """gnt-instance remove"""
309
  AssertCommand(["gnt-instance", "remove", "-f", instance.name])
310

    
311

    
312
@InstanceCheck(INST_DOWN, INST_UP, FIRST_ARG)
313
def TestInstanceStartup(instance):
314
  """gnt-instance startup"""
315
  AssertCommand(["gnt-instance", "startup", instance.name])
316

    
317

    
318
@InstanceCheck(INST_UP, INST_DOWN, FIRST_ARG)
319
def TestInstanceShutdown(instance):
320
  """gnt-instance shutdown"""
321
  AssertCommand(["gnt-instance", "shutdown", instance.name])
322

    
323

    
324
@InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
325
def TestInstanceReboot(instance):
326
  """gnt-instance reboot"""
327
  options = qa_config.get("options", {})
328
  reboot_types = options.get("reboot-types", constants.REBOOT_TYPES)
329
  name = instance.name
330
  for rtype in reboot_types:
331
    AssertCommand(["gnt-instance", "reboot", "--type=%s" % rtype, name])
332

    
333
  AssertCommand(["gnt-instance", "shutdown", name])
334
  qa_utils.RunInstanceCheck(instance, False)
335
  AssertCommand(["gnt-instance", "reboot", name])
336

    
337
  master = qa_config.GetMasterNode()
338
  cmd = ["gnt-instance", "list", "--no-headers", "-o", "status", name]
339
  result_output = qa_utils.GetCommandOutput(master.primary,
340
                                            utils.ShellQuoteArgs(cmd))
341
  AssertEqual(result_output.strip(), constants.INSTST_RUNNING)
342

    
343

    
344
@InstanceCheck(INST_DOWN, INST_DOWN, FIRST_ARG)
345
def TestInstanceReinstall(instance):
346
  """gnt-instance reinstall"""
347
  if instance.disk_template == constants.DT_DISKLESS:
348
    print qa_utils.FormatInfo("Test not supported for diskless instances")
349
    return
350

    
351
  AssertCommand(["gnt-instance", "reinstall", "-f", instance.name])
352

    
353
  # Test with non-existant OS definition
354
  AssertCommand(["gnt-instance", "reinstall", "-f",
355
                 "--os-type=NonExistantOsForQa",
356
                 instance.name],
357
                fail=True)
358

    
359

    
360
def _ReadSsconfInstanceList():
361
  """Reads ssconf_instance_list from the master node.
362

363
  """
364
  master = qa_config.GetMasterNode()
365

    
366
  ssconf_path = utils.PathJoin(pathutils.DATA_DIR,
367
                               "ssconf_%s" % constants.SS_INSTANCE_LIST)
368

    
369
  cmd = ["cat", qa_utils.MakeNodePath(master, ssconf_path)]
370

    
371
  return qa_utils.GetCommandOutput(master.primary,
372
                                   utils.ShellQuoteArgs(cmd)).splitlines()
373

    
374

    
375
def _CheckSsconfInstanceList(instance):
376
  """Checks if a certain instance is in the ssconf instance list.
377

378
  @type instance: string
379
  @param instance: Instance name
380

381
  """
382
  AssertIn(qa_utils.ResolveInstanceName(instance),
383
           _ReadSsconfInstanceList())
384

    
385

    
386
@InstanceCheck(INST_DOWN, INST_DOWN, FIRST_ARG)
387
def TestInstanceRenameAndBack(rename_source, rename_target):
388
  """gnt-instance rename
389

390
  This must leave the instance with the original name, not the target
391
  name.
392

393
  """
394
  _CheckSsconfInstanceList(rename_source)
395

    
396
  # first do a rename to a different actual name, expecting it to fail
397
  qa_utils.AddToEtcHosts(["meeeeh-not-exists", rename_target])
398
  try:
399
    AssertCommand(["gnt-instance", "rename", rename_source, rename_target],
400
                  fail=True)
401
    _CheckSsconfInstanceList(rename_source)
402
  finally:
403
    qa_utils.RemoveFromEtcHosts(["meeeeh-not-exists", rename_target])
404

    
405
  # Check instance volume tags correctly updated
406
  # FIXME: this is LVM specific!
407
  info = _GetInstanceInfo(rename_source)
408
  tags_cmd = ("lvs -o tags --noheadings %s | grep " %
409
              (" ".join(info["volumes"]), ))
410

    
411
  # and now rename instance to rename_target...
412
  AssertCommand(["gnt-instance", "rename", rename_source, rename_target])
413
  _CheckSsconfInstanceList(rename_target)
414
  qa_utils.RunInstanceCheck(rename_source, False)
415
  qa_utils.RunInstanceCheck(rename_target, False)
416

    
417
  # NOTE: tags might not be the exactly as the instance name, due to
418
  # charset restrictions; hence the test might be flaky
419
  if rename_source != rename_target:
420
    for node in info["nodes"]:
421
      AssertCommand(tags_cmd + rename_source, node=node, fail=True)
422
      AssertCommand(tags_cmd + rename_target, node=node, fail=False)
423

    
424
  # and back
425
  AssertCommand(["gnt-instance", "rename", rename_target, rename_source])
426
  _CheckSsconfInstanceList(rename_source)
427
  qa_utils.RunInstanceCheck(rename_target, False)
428

    
429
  if rename_source != rename_target:
430
    for node in info["nodes"]:
431
      AssertCommand(tags_cmd + rename_source, node=node, fail=False)
432
      AssertCommand(tags_cmd + rename_target, node=node, fail=True)
433

    
434

    
435
@InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
436
def TestInstanceFailover(instance):
437
  """gnt-instance failover"""
438
  if not IsFailoverSupported(instance):
439
    print qa_utils.FormatInfo("Instance doesn't support failover, skipping"
440
                              " test")
441
    return
442

    
443
  cmd = ["gnt-instance", "failover", "--force", instance.name]
444

    
445
  # failover ...
446
  AssertCommand(cmd)
447
  qa_utils.RunInstanceCheck(instance, True)
448

    
449
  # ... and back
450
  AssertCommand(cmd)
451

    
452

    
453
@InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
454
def TestInstanceMigrate(instance, toggle_always_failover=True):
455
  """gnt-instance migrate"""
456
  if not IsMigrationSupported(instance):
457
    print qa_utils.FormatInfo("Instance doesn't support migration, skipping"
458
                              " test")
459
    return
460

    
461
  cmd = ["gnt-instance", "migrate", "--force", instance.name]
462
  af_par = constants.BE_ALWAYS_FAILOVER
463
  af_field = "be/" + constants.BE_ALWAYS_FAILOVER
464
  af_init_val = _GetBoolInstanceField(instance.name, af_field)
465

    
466
  # migrate ...
467
  AssertCommand(cmd)
468
  # TODO: Verify the choice between failover and migration
469
  qa_utils.RunInstanceCheck(instance, True)
470

    
471
  # ... and back (possibly with always_failover toggled)
472
  if toggle_always_failover:
473
    AssertCommand(["gnt-instance", "modify", "-B",
474
                   ("%s=%s" % (af_par, not af_init_val)),
475
                   instance.name])
476
  AssertCommand(cmd)
477
  # TODO: Verify the choice between failover and migration
478
  qa_utils.RunInstanceCheck(instance, True)
479
  if toggle_always_failover:
480
    AssertCommand(["gnt-instance", "modify", "-B",
481
                   ("%s=%s" % (af_par, af_init_val)), instance.name])
482

    
483
  # TODO: Split into multiple tests
484
  AssertCommand(["gnt-instance", "shutdown", instance.name])
485
  qa_utils.RunInstanceCheck(instance, False)
486
  AssertCommand(cmd, fail=True)
487
  AssertCommand(["gnt-instance", "migrate", "--force", "--allow-failover",
488
                 instance.name])
489
  AssertCommand(["gnt-instance", "start", instance.name])
490
  AssertCommand(cmd)
491
  # @InstanceCheck enforces the check that the instance is running
492
  qa_utils.RunInstanceCheck(instance, True)
493

    
494
  AssertCommand(["gnt-instance", "modify", "-B",
495
                 ("%s=%s" %
496
                  (constants.BE_ALWAYS_FAILOVER, constants.VALUE_TRUE)),
497
                 instance.name])
498

    
499
  AssertCommand(cmd)
500
  qa_utils.RunInstanceCheck(instance, True)
501
  # TODO: Verify that a failover has been done instead of a migration
502

    
503
  # TODO: Verify whether the default value is restored here (not hardcoded)
504
  AssertCommand(["gnt-instance", "modify", "-B",
505
                 ("%s=%s" %
506
                  (constants.BE_ALWAYS_FAILOVER, constants.VALUE_FALSE)),
507
                 instance.name])
508

    
509
  AssertCommand(cmd)
510
  qa_utils.RunInstanceCheck(instance, True)
511

    
512

    
513
def TestInstanceInfo(instance):
514
  """gnt-instance info"""
515
  AssertCommand(["gnt-instance", "info", instance.name])
516

    
517

    
518
@InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
519
def TestInstanceModify(instance):
520
  """gnt-instance modify"""
521
  default_hv = qa_config.GetDefaultHypervisor()
522

    
523
  # Assume /sbin/init exists on all systems
524
  test_kernel = "/sbin/init"
525
  test_initrd = test_kernel
526

    
527
  orig_maxmem = qa_config.get(constants.BE_MAXMEM)
528
  orig_minmem = qa_config.get(constants.BE_MINMEM)
529
  #orig_bridge = qa_config.get("bridge", "xen-br0")
530

    
531
  args = [
532
    ["-B", "%s=128" % constants.BE_MINMEM],
533
    ["-B", "%s=128" % constants.BE_MAXMEM],
534
    ["-B", "%s=%s,%s=%s" % (constants.BE_MINMEM, orig_minmem,
535
                            constants.BE_MAXMEM, orig_maxmem)],
536
    ["-B", "%s=2" % constants.BE_VCPUS],
537
    ["-B", "%s=1" % constants.BE_VCPUS],
538
    ["-B", "%s=%s" % (constants.BE_VCPUS, constants.VALUE_DEFAULT)],
539
    ["-B", "%s=%s" % (constants.BE_ALWAYS_FAILOVER, constants.VALUE_TRUE)],
540
    ["-B", "%s=%s" % (constants.BE_ALWAYS_FAILOVER, constants.VALUE_DEFAULT)],
541

    
542
    ["-H", "%s=%s" % (constants.HV_KERNEL_PATH, test_kernel)],
543
    ["-H", "%s=%s" % (constants.HV_KERNEL_PATH, constants.VALUE_DEFAULT)],
544

    
545
    # TODO: bridge tests
546
    #["--bridge", "xen-br1"],
547
    #["--bridge", orig_bridge],
548
    ]
549

    
550
  if default_hv == constants.HT_XEN_PVM:
551
    args.extend([
552
      ["-H", "%s=%s" % (constants.HV_INITRD_PATH, test_initrd)],
553
      ["-H", "no_%s" % (constants.HV_INITRD_PATH, )],
554
      ["-H", "%s=%s" % (constants.HV_INITRD_PATH, constants.VALUE_DEFAULT)],
555
      ])
556
  elif default_hv == constants.HT_XEN_HVM:
557
    args.extend([
558
      ["-H", "%s=acn" % constants.HV_BOOT_ORDER],
559
      ["-H", "%s=%s" % (constants.HV_BOOT_ORDER, constants.VALUE_DEFAULT)],
560
      ])
561

    
562
  for alist in args:
563
    AssertCommand(["gnt-instance", "modify"] + alist + [instance.name])
564

    
565
  # check no-modify
566
  AssertCommand(["gnt-instance", "modify", instance.name], fail=True)
567

    
568
  # Marking offline while instance is running must fail...
569
  AssertCommand(["gnt-instance", "modify", "--offline", instance.name],
570
                 fail=True)
571

    
572
  # ...while making it online is ok, and should work
573
  AssertCommand(["gnt-instance", "modify", "--online", instance.name])
574

    
575

    
576
@InstanceCheck(INST_DOWN, INST_DOWN, FIRST_ARG)
577
def TestInstanceStoppedModify(instance):
578
  """gnt-instance modify (stopped instance)"""
579
  name = instance.name
580

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

    
584
  # Mark instance as offline
585
  AssertCommand(["gnt-instance", "modify", "--offline", name])
586

    
587
  # When the instance is offline shutdown should only work with --force,
588
  # while start should never work
589
  AssertCommand(["gnt-instance", "shutdown", name], fail=True)
590
  AssertCommand(["gnt-instance", "shutdown", "--force", name])
591
  AssertCommand(["gnt-instance", "start", name], fail=True)
592
  AssertCommand(["gnt-instance", "start", "--force", name], fail=True)
593

    
594
  # Also do offline to offline
595
  AssertCommand(["gnt-instance", "modify", "--offline", name])
596

    
597
  # And online again
598
  AssertCommand(["gnt-instance", "modify", "--online", name])
599

    
600

    
601
@InstanceCheck(INST_DOWN, INST_DOWN, FIRST_ARG)
602
def TestInstanceConvertDiskToPlain(instance, inodes):
603
  """gnt-instance modify -t"""
604
  name = instance.name
605

    
606
  template = instance.disk_template
607
  if template != constants.DT_DRBD8:
608
    print qa_utils.FormatInfo("Unsupported template %s, skipping conversion"
609
                              " test" % template)
610
    return
611

    
612
  assert len(inodes) == 2
613
  AssertCommand(["gnt-instance", "modify", "-t", constants.DT_PLAIN, name])
614
  AssertCommand(["gnt-instance", "modify", "-t", constants.DT_DRBD8,
615
                 "-n", inodes[1].primary, name])
616

    
617

    
618
@InstanceCheck(INST_DOWN, INST_DOWN, FIRST_ARG)
619
def TestInstanceGrowDisk(instance):
620
  """gnt-instance grow-disk"""
621
  if qa_config.GetExclusiveStorage():
622
    print qa_utils.FormatInfo("Test not supported with exclusive_storage")
623
    return
624

    
625
  if instance.disk_template == constants.DT_DISKLESS:
626
    print qa_utils.FormatInfo("Test not supported for diskless instances")
627
    return
628

    
629
  name = instance.name
630
  all_size = qa_config.get("disk")
631
  all_grow = qa_config.get("disk-growth")
632

    
633
  if not all_grow:
634
    # missing disk sizes but instance grow disk has been enabled,
635
    # let's set fixed/nomimal growth
636
    all_grow = ["128M" for _ in all_size]
637

    
638
  for idx, (size, grow) in enumerate(zip(all_size, all_grow)):
639
    # succeed in grow by amount
640
    AssertCommand(["gnt-instance", "grow-disk", name, str(idx), grow])
641
    # fail in grow to the old size
642
    AssertCommand(["gnt-instance", "grow-disk", "--absolute", name, str(idx),
643
                   size], fail=True)
644
    # succeed to grow to old size + 2 * growth
645
    int_size = utils.ParseUnit(size)
646
    int_grow = utils.ParseUnit(grow)
647
    AssertCommand(["gnt-instance", "grow-disk", "--absolute", name, str(idx),
648
                   str(int_size + 2 * int_grow)])
649

    
650

    
651
def TestInstanceList():
652
  """gnt-instance list"""
653
  qa_utils.GenericQueryTest("gnt-instance", query.INSTANCE_FIELDS.keys())
654

    
655

    
656
def TestInstanceListFields():
657
  """gnt-instance list-fields"""
658
  qa_utils.GenericQueryFieldsTest("gnt-instance", query.INSTANCE_FIELDS.keys())
659

    
660

    
661
@InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
662
def TestInstanceConsole(instance):
663
  """gnt-instance console"""
664
  AssertCommand(["gnt-instance", "console", "--show-cmd", instance.name])
665

    
666

    
667
@InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
668
def TestReplaceDisks(instance, curr_nodes, other_nodes):
669
  """gnt-instance replace-disks"""
670
  def buildcmd(args):
671
    cmd = ["gnt-instance", "replace-disks"]
672
    cmd.extend(args)
673
    cmd.append(instance.name)
674
    return cmd
675

    
676
  if not IsDiskReplacingSupported(instance):
677
    print qa_utils.FormatInfo("Instance doesn't support disk replacing,"
678
                              " skipping test")
679
    return
680

    
681
  # Currently all supported templates have one primary and one secondary node
682
  assert len(curr_nodes) == 2
683
  snode = curr_nodes[1]
684
  assert len(other_nodes) == 1
685
  othernode = other_nodes[0]
686

    
687
  options = qa_config.get("options", {})
688
  use_ialloc = options.get("use-iallocators", True)
689
  for data in [
690
    ["-p"],
691
    ["-s"],
692
    # A placeholder; the actual command choice depends on use_ialloc
693
    None,
694
    # Restore the original secondary
695
    ["--new-secondary=%s" % snode.primary],
696
    ]:
697
    if data is None:
698
      if use_ialloc:
699
        data = ["-I", constants.DEFAULT_IALLOCATOR_SHORTCUT]
700
      else:
701
        data = ["--new-secondary=%s" % othernode.primary]
702
    AssertCommand(buildcmd(data))
703

    
704
  AssertCommand(buildcmd(["-a"]))
705
  AssertCommand(["gnt-instance", "stop", instance.name])
706
  AssertCommand(buildcmd(["-a"]), fail=True)
707
  AssertCommand(["gnt-instance", "activate-disks", instance.name])
708
  AssertCommand(["gnt-instance", "activate-disks", "--wait-for-sync",
709
                 instance.name])
710
  AssertCommand(buildcmd(["-a"]))
711
  AssertCommand(["gnt-instance", "start", instance.name])
712

    
713

    
714
def _AssertRecreateDisks(cmdargs, instance, fail=False, check=True,
715
                         destroy=True):
716
  """Execute gnt-instance recreate-disks and check the result
717

718
  @param cmdargs: Arguments (instance name excluded)
719
  @param instance: Instance to operate on
720
  @param fail: True if the command is expected to fail
721
  @param check: If True and fail is False, check that the disks work
722
  @prama destroy: If True, destroy the old disks first
723

724
  """
725
  if destroy:
726
    _DestroyInstanceVolumes(instance)
727
  AssertCommand((["gnt-instance", "recreate-disks"] + cmdargs +
728
                 [instance.name]), fail)
729
  if not fail and check:
730
    # Quick check that the disks are there
731
    AssertCommand(["gnt-instance", "activate-disks", instance.name])
732
    AssertCommand(["gnt-instance", "activate-disks", "--wait-for-sync",
733
                   instance.name])
734
    AssertCommand(["gnt-instance", "deactivate-disks", instance.name])
735

    
736

    
737
@InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
738
def TestRecreateDisks(instance, inodes, othernodes):
739
  """gnt-instance recreate-disks
740

741
  @param instance: Instance to work on
742
  @param inodes: List of the current nodes of the instance
743
  @param othernodes: list/tuple of nodes where to temporarily recreate disks
744

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

    
775

    
776
@InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
777
def TestInstanceExport(instance, node):
778
  """gnt-backup export -n ..."""
779
  name = instance.name
780
  AssertCommand(["gnt-backup", "export", "-n", node.primary, name])
781
  return qa_utils.ResolveInstanceName(name)
782

    
783

    
784
@InstanceCheck(None, INST_DOWN, FIRST_ARG)
785
def TestInstanceExportWithRemove(instance, node):
786
  """gnt-backup export --remove-instance"""
787
  AssertCommand(["gnt-backup", "export", "-n", node.primary,
788
                 "--remove-instance", instance.name])
789

    
790

    
791
@InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
792
def TestInstanceExportNoTarget(instance):
793
  """gnt-backup export (without target node, should fail)"""
794
  AssertCommand(["gnt-backup", "export", instance.name], fail=True)
795

    
796

    
797
@InstanceCheck(None, INST_DOWN, FIRST_ARG)
798
def TestInstanceImport(newinst, node, expnode, name):
799
  """gnt-backup import"""
800
  templ = constants.DT_PLAIN
801
  cmd = (["gnt-backup", "import",
802
          "--disk-template=%s" % templ,
803
          "--no-ip-check",
804
          "--src-node=%s" % expnode.primary,
805
          "--src-dir=%s/%s" % (pathutils.EXPORT_DIR, name),
806
          "--node=%s" % node.primary] +
807
         _GetGenericAddParameters(newinst, templ,
808
                                  force_mac=constants.VALUE_GENERATE))
809
  cmd.append(newinst.name)
810
  AssertCommand(cmd)
811
  newinst.SetDiskTemplate(templ)
812

    
813

    
814
def TestBackupList(expnode):
815
  """gnt-backup list"""
816
  AssertCommand(["gnt-backup", "list", "--node=%s" % expnode.primary])
817

    
818
  qa_utils.GenericQueryTest("gnt-backup", query.EXPORT_FIELDS.keys(),
819
                            namefield=None, test_unknown=False)
820

    
821

    
822
def TestBackupListFields():
823
  """gnt-backup list-fields"""
824
  qa_utils.GenericQueryFieldsTest("gnt-backup", query.EXPORT_FIELDS.keys())
825

    
826

    
827
def TestRemoveInstanceOfflineNode(instance, snode, set_offline, set_online):
828
  """gtn-instance remove with an off-line node
829

830
  @param instance: instance
831
  @param snode: secondary node, to be set offline
832
  @param set_offline: function to call to set the node off-line
833
  @param set_online: function to call to set the node on-line
834

835
  """
836
  info = _GetInstanceInfo(instance.name)
837
  set_offline(snode)
838
  try:
839
    TestInstanceRemove(instance)
840
  finally:
841
    set_online(snode)
842
  # Clean up the disks on the offline node
843
  for minor in info["drbd-minors"][snode.primary]:
844
    AssertCommand(["drbdsetup", str(minor), "down"], node=snode)
845
  AssertCommand(["lvremove", "-f"] + info["volumes"], node=snode)