Statistics
| Branch: | Tag: | Revision:

root / qa / qa_instance.py @ 2cbcf95d

History | View | Annotate | Download (27.2 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 TestInstanceAddDiskless(nodes):
294
  """gnt-instance add -t diskless"""
295
  assert len(nodes) == 1
296
  return _DiskTest(nodes[0].primary, constants.DT_DISKLESS)
297

    
298

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

    
304

    
305
@InstanceCheck(INST_DOWN, INST_UP, FIRST_ARG)
306
def TestInstanceStartup(instance):
307
  """gnt-instance startup"""
308
  AssertCommand(["gnt-instance", "startup", instance.name])
309

    
310

    
311
@InstanceCheck(INST_UP, INST_DOWN, FIRST_ARG)
312
def TestInstanceShutdown(instance):
313
  """gnt-instance shutdown"""
314
  AssertCommand(["gnt-instance", "shutdown", instance.name])
315

    
316

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

    
326
  AssertCommand(["gnt-instance", "shutdown", name])
327
  qa_utils.RunInstanceCheck(instance, False)
328
  AssertCommand(["gnt-instance", "reboot", name])
329

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

    
336

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

    
344
  AssertCommand(["gnt-instance", "reinstall", "-f", instance.name])
345

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

    
352

    
353
def _ReadSsconfInstanceList():
354
  """Reads ssconf_instance_list from the master node.
355

356
  """
357
  master = qa_config.GetMasterNode()
358

    
359
  ssconf_path = utils.PathJoin(pathutils.DATA_DIR,
360
                               "ssconf_%s" % constants.SS_INSTANCE_LIST)
361

    
362
  cmd = ["cat", qa_utils.MakeNodePath(master, ssconf_path)]
363

    
364
  return qa_utils.GetCommandOutput(master.primary,
365
                                   utils.ShellQuoteArgs(cmd)).splitlines()
366

    
367

    
368
def _CheckSsconfInstanceList(instance):
369
  """Checks if a certain instance is in the ssconf instance list.
370

371
  @type instance: string
372
  @param instance: Instance name
373

374
  """
375
  AssertIn(qa_utils.ResolveInstanceName(instance),
376
           _ReadSsconfInstanceList())
377

    
378

    
379
@InstanceCheck(INST_DOWN, INST_DOWN, FIRST_ARG)
380
def TestInstanceRenameAndBack(rename_source, rename_target):
381
  """gnt-instance rename
382

383
  This must leave the instance with the original name, not the target
384
  name.
385

386
  """
387
  _CheckSsconfInstanceList(rename_source)
388

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

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

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

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

    
417
  # and back
418
  AssertCommand(["gnt-instance", "rename", rename_target, rename_source])
419
  _CheckSsconfInstanceList(rename_source)
420
  qa_utils.RunInstanceCheck(rename_target, False)
421

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

    
427

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

    
436
  cmd = ["gnt-instance", "failover", "--force", instance.name]
437

    
438
  # failover ...
439
  AssertCommand(cmd)
440
  qa_utils.RunInstanceCheck(instance, True)
441

    
442
  # ... and back
443
  AssertCommand(cmd)
444

    
445

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

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

    
459
  # migrate ...
460
  AssertCommand(cmd)
461
  # TODO: Verify the choice between failover and migration
462
  qa_utils.RunInstanceCheck(instance, True)
463

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

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

    
487
  AssertCommand(["gnt-instance", "modify", "-B",
488
                 ("%s=%s" %
489
                  (constants.BE_ALWAYS_FAILOVER, constants.VALUE_TRUE)),
490
                 instance.name])
491

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

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

    
502
  AssertCommand(cmd)
503
  qa_utils.RunInstanceCheck(instance, True)
504

    
505

    
506
def TestInstanceInfo(instance):
507
  """gnt-instance info"""
508
  AssertCommand(["gnt-instance", "info", instance.name])
509

    
510

    
511
@InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
512
def TestInstanceModify(instance):
513
  """gnt-instance modify"""
514
  default_hv = qa_config.GetDefaultHypervisor()
515

    
516
  # Assume /sbin/init exists on all systems
517
  test_kernel = "/sbin/init"
518
  test_initrd = test_kernel
519

    
520
  orig_maxmem = qa_config.get(constants.BE_MAXMEM)
521
  orig_minmem = qa_config.get(constants.BE_MINMEM)
522
  #orig_bridge = qa_config.get("bridge", "xen-br0")
523

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

    
535
    ["-H", "%s=%s" % (constants.HV_KERNEL_PATH, test_kernel)],
536
    ["-H", "%s=%s" % (constants.HV_KERNEL_PATH, constants.VALUE_DEFAULT)],
537

    
538
    # TODO: bridge tests
539
    #["--bridge", "xen-br1"],
540
    #["--bridge", orig_bridge],
541
    ]
542

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

    
555
  for alist in args:
556
    AssertCommand(["gnt-instance", "modify"] + alist + [instance.name])
557

    
558
  # check no-modify
559
  AssertCommand(["gnt-instance", "modify", instance.name], fail=True)
560

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

    
565
  # ...while making it online is ok, and should work
566
  AssertCommand(["gnt-instance", "modify", "--online", instance.name])
567

    
568

    
569
@InstanceCheck(INST_DOWN, INST_DOWN, FIRST_ARG)
570
def TestInstanceStoppedModify(instance):
571
  """gnt-instance modify (stopped instance)"""
572
  name = instance.name
573

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

    
577
  # Mark instance as offline
578
  AssertCommand(["gnt-instance", "modify", "--offline", name])
579

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

    
587
  # Also do offline to offline
588
  AssertCommand(["gnt-instance", "modify", "--offline", name])
589

    
590
  # And online again
591
  AssertCommand(["gnt-instance", "modify", "--online", name])
592

    
593

    
594
@InstanceCheck(INST_DOWN, INST_DOWN, FIRST_ARG)
595
def TestInstanceConvertDiskToPlain(instance, inodes):
596
  """gnt-instance modify -t"""
597
  name = instance.name
598

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

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

    
610

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

    
618
  if instance.disk_template == constants.DT_DISKLESS:
619
    print qa_utils.FormatInfo("Test not supported for diskless instances")
620
    return
621

    
622
  name = instance.name
623
  all_size = qa_config.get("disk")
624
  all_grow = qa_config.get("disk-growth")
625

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

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

    
643

    
644
def TestInstanceList():
645
  """gnt-instance list"""
646
  qa_utils.GenericQueryTest("gnt-instance", query.INSTANCE_FIELDS.keys())
647

    
648

    
649
def TestInstanceListFields():
650
  """gnt-instance list-fields"""
651
  qa_utils.GenericQueryFieldsTest("gnt-instance", query.INSTANCE_FIELDS.keys())
652

    
653

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

    
659

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

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

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

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

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

    
706

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

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

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

    
729

    
730
@InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
731
def TestRecreateDisks(instance, inodes, othernodes):
732
  """gnt-instance recreate-disks
733

734
  @param instance: Instance to work on
735
  @param inodes: List of the current nodes of the instance
736
  @param othernodes: list/tuple of nodes where to temporarily recreate disks
737

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

    
768

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

    
776

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

    
783

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

    
789

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

    
806

    
807
def TestBackupList(expnode):
808
  """gnt-backup list"""
809
  AssertCommand(["gnt-backup", "list", "--node=%s" % expnode.primary])
810

    
811
  qa_utils.GenericQueryTest("gnt-backup", query.EXPORT_FIELDS.keys(),
812
                            namefield=None, test_unknown=False)
813

    
814

    
815
def TestBackupListFields():
816
  """gnt-backup list-fields"""
817
  qa_utils.GenericQueryFieldsTest("gnt-backup", query.EXPORT_FIELDS.keys())
818

    
819

    
820
def TestRemoveInstanceOfflineNode(instance, snode, set_offline, set_online):
821
  """gtn-instance remove with an off-line node
822

823
  @param instance: instance
824
  @param snode: secondary node, to be set offline
825
  @param set_offline: function to call to set the node off-line
826
  @param set_online: function to call to set the node on-line
827

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