Statistics
| Branch: | Tag: | Revision:

root / qa / qa_instance.py @ e2e98c6e

History | View | Annotate | Download (27.5 kB)

1
#
2
#
3

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

    
21

    
22
"""Instance related QA tests.
23

24
"""
25

    
26
import operator
27
import re
28

    
29
from ganeti import utils
30
from ganeti import constants
31
from ganeti import query
32
from ganeti import pathutils
33

    
34
import qa_config
35
import qa_utils
36
import qa_error
37

    
38
from qa_utils import AssertIn, AssertCommand, AssertEqual
39
from qa_utils import InstanceCheck, INST_DOWN, INST_UP, FIRST_ARG, RETURN_VALUE
40

    
41

    
42
def _GetDiskStatePath(disk):
43
  return "/sys/block/%s/device/state" % disk
44

    
45

    
46
def _GetGenericAddParameters(inst, force_mac=None):
47
  params = ["-B"]
48
  params.append("%s=%s,%s=%s" % (constants.BE_MINMEM,
49
                                 qa_config.get(constants.BE_MINMEM),
50
                                 constants.BE_MAXMEM,
51
                                 qa_config.get(constants.BE_MAXMEM)))
52
  for idx, size in enumerate(qa_config.get("disk")):
53
    params.extend(["--disk", "%s:size=%s" % (idx, size)])
54

    
55
  # Set static MAC address if configured
56
  if force_mac:
57
    nic0_mac = force_mac
58
  else:
59
    nic0_mac = qa_config.GetInstanceNicMac(inst)
60
  if nic0_mac:
61
    params.extend(["--net", "0:mac=%s" % nic0_mac])
62

    
63
  return params
64

    
65

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

    
76
    AssertCommand(cmd, fail=fail)
77

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

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

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

    
92

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

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

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

    
146

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

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

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

    
161

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

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

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

    
177

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

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

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

    
197

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

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

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

    
219

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

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

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

    
250

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

    
255

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

    
260

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

    
265

    
266
def IsDiskSupported(instance):
267
  templ = qa_config.GetInstanceTemplate(instance)
268
  return templ != constants.DT_DISKLESS
269

    
270

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

    
279

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

    
287

    
288
@InstanceCheck(None, INST_DOWN, FIRST_ARG)
289
def TestInstanceRemove(instance):
290
  """gnt-instance remove"""
291
  AssertCommand(["gnt-instance", "remove", "-f", instance["name"]])
292

    
293
  qa_config.ReleaseInstance(instance)
294

    
295

    
296
@InstanceCheck(INST_DOWN, INST_UP, FIRST_ARG)
297
def TestInstanceStartup(instance):
298
  """gnt-instance startup"""
299
  AssertCommand(["gnt-instance", "startup", instance["name"]])
300

    
301

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

    
307

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

    
317
  AssertCommand(["gnt-instance", "shutdown", name])
318
  qa_utils.RunInstanceCheck(instance, False)
319
  AssertCommand(["gnt-instance", "reboot", name])
320

    
321
  master = qa_config.GetMasterNode()
322
  cmd = ["gnt-instance", "list", "--no-headers", "-o", "status", name]
323
  result_output = qa_utils.GetCommandOutput(master["primary"],
324
                                            utils.ShellQuoteArgs(cmd))
325
  AssertEqual(result_output.strip(), constants.INSTST_RUNNING)
326

    
327

    
328
@InstanceCheck(INST_DOWN, INST_DOWN, FIRST_ARG)
329
def TestInstanceReinstall(instance):
330
  """gnt-instance reinstall"""
331
  AssertCommand(["gnt-instance", "reinstall", "-f", instance["name"]])
332

    
333
  # Test with non-existant OS definition
334
  AssertCommand(["gnt-instance", "reinstall", "-f",
335
                 "--os-type=NonExistantOsForQa",
336
                 instance["name"]],
337
                fail=True)
338

    
339

    
340
def _ReadSsconfInstanceList():
341
  """Reads ssconf_instance_list from the master node.
342

343
  """
344
  master = qa_config.GetMasterNode()
345

    
346
  cmd = ["cat", utils.PathJoin(pathutils.DATA_DIR,
347
                               "ssconf_%s" % constants.SS_INSTANCE_LIST)]
348

    
349
  return qa_utils.GetCommandOutput(master["primary"],
350
                                   utils.ShellQuoteArgs(cmd)).splitlines()
351

    
352

    
353
def _CheckSsconfInstanceList(instance):
354
  """Checks if a certain instance is in the ssconf instance list.
355

356
  @type instance: string
357
  @param instance: Instance name
358

359
  """
360
  AssertIn(qa_utils.ResolveInstanceName(instance),
361
           _ReadSsconfInstanceList())
362

    
363

    
364
@InstanceCheck(INST_DOWN, INST_DOWN, FIRST_ARG)
365
def TestInstanceRenameAndBack(rename_source, rename_target):
366
  """gnt-instance rename
367

368
  This must leave the instance with the original name, not the target
369
  name.
370

371
  """
372
  _CheckSsconfInstanceList(rename_source)
373

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

    
383
  # Check instance volume tags correctly updated
384
  # FIXME: this is LVM specific!
385
  info = _GetInstanceInfo(rename_source)
386
  tags_cmd = ("lvs -o tags --noheadings %s | grep " %
387
              (" ".join(info["volumes"]), ))
388

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

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

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

    
407
  if rename_source != rename_target:
408
    for node in info["nodes"]:
409
      AssertCommand(tags_cmd + rename_source, node=node, fail=False)
410
      AssertCommand(tags_cmd + rename_target, node=node, fail=True)
411

    
412

    
413
@InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
414
def TestInstanceFailover(instance):
415
  """gnt-instance failover"""
416
  if not IsFailoverSupported(instance):
417
    print qa_utils.FormatInfo("Instance doesn't support failover, skipping"
418
                              " test")
419
    return
420

    
421
  cmd = ["gnt-instance", "failover", "--force", instance["name"]]
422

    
423
  # failover ...
424
  AssertCommand(cmd)
425
  qa_utils.RunInstanceCheck(instance, True)
426

    
427
  # ... and back
428
  AssertCommand(cmd)
429

    
430

    
431
@InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
432
def TestInstanceMigrate(instance, toggle_always_failover=True):
433
  """gnt-instance migrate"""
434
  if not IsMigrationSupported(instance):
435
    print qa_utils.FormatInfo("Instance doesn't support migration, skipping"
436
                              " test")
437
    return
438

    
439
  cmd = ["gnt-instance", "migrate", "--force", instance["name"]]
440
  af_par = constants.BE_ALWAYS_FAILOVER
441
  af_field = "be/" + constants.BE_ALWAYS_FAILOVER
442
  af_init_val = _GetBoolInstanceField(instance["name"], af_field)
443

    
444
  # migrate ...
445
  AssertCommand(cmd)
446
  # TODO: Verify the choice between failover and migration
447
  qa_utils.RunInstanceCheck(instance, True)
448

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

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

    
472
  AssertCommand(["gnt-instance", "modify", "-B",
473
                 ("%s=%s" %
474
                  (constants.BE_ALWAYS_FAILOVER, constants.VALUE_TRUE)),
475
                 instance["name"]])
476

    
477
  AssertCommand(cmd)
478
  qa_utils.RunInstanceCheck(instance, True)
479
  # TODO: Verify that a failover has been done instead of a migration
480

    
481
  # TODO: Verify whether the default value is restored here (not hardcoded)
482
  AssertCommand(["gnt-instance", "modify", "-B",
483
                 ("%s=%s" %
484
                  (constants.BE_ALWAYS_FAILOVER, constants.VALUE_FALSE)),
485
                 instance["name"]])
486

    
487
  AssertCommand(cmd)
488
  qa_utils.RunInstanceCheck(instance, True)
489

    
490

    
491
def TestInstanceInfo(instance):
492
  """gnt-instance info"""
493
  AssertCommand(["gnt-instance", "info", instance["name"]])
494

    
495

    
496
@InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
497
def TestInstanceModify(instance):
498
  """gnt-instance modify"""
499
  default_hv = qa_config.GetDefaultHypervisor()
500

    
501
  # Assume /sbin/init exists on all systems
502
  test_kernel = "/sbin/init"
503
  test_initrd = test_kernel
504

    
505
  orig_maxmem = qa_config.get(constants.BE_MAXMEM)
506
  orig_minmem = qa_config.get(constants.BE_MINMEM)
507
  #orig_bridge = qa_config.get("bridge", "xen-br0")
508

    
509
  args = [
510
    ["-B", "%s=128" % constants.BE_MINMEM],
511
    ["-B", "%s=128" % constants.BE_MAXMEM],
512
    ["-B", "%s=%s,%s=%s" % (constants.BE_MINMEM, orig_minmem,
513
                            constants.BE_MAXMEM, orig_maxmem)],
514
    ["-B", "%s=2" % constants.BE_VCPUS],
515
    ["-B", "%s=1" % constants.BE_VCPUS],
516
    ["-B", "%s=%s" % (constants.BE_VCPUS, constants.VALUE_DEFAULT)],
517
    ["-B", "%s=%s" % (constants.BE_ALWAYS_FAILOVER, constants.VALUE_TRUE)],
518
    ["-B", "%s=%s" % (constants.BE_ALWAYS_FAILOVER, constants.VALUE_DEFAULT)],
519

    
520
    ["-H", "%s=%s" % (constants.HV_KERNEL_PATH, test_kernel)],
521
    ["-H", "%s=%s" % (constants.HV_KERNEL_PATH, constants.VALUE_DEFAULT)],
522

    
523
    # TODO: bridge tests
524
    #["--bridge", "xen-br1"],
525
    #["--bridge", orig_bridge],
526
    ]
527

    
528
  if default_hv == constants.HT_XEN_PVM:
529
    args.extend([
530
      ["-H", "%s=%s" % (constants.HV_INITRD_PATH, test_initrd)],
531
      ["-H", "no_%s" % (constants.HV_INITRD_PATH, )],
532
      ["-H", "%s=%s" % (constants.HV_INITRD_PATH, constants.VALUE_DEFAULT)],
533
      ])
534
  elif default_hv == constants.HT_XEN_HVM:
535
    args.extend([
536
      ["-H", "%s=acn" % constants.HV_BOOT_ORDER],
537
      ["-H", "%s=%s" % (constants.HV_BOOT_ORDER, constants.VALUE_DEFAULT)],
538
      ])
539

    
540
  for alist in args:
541
    AssertCommand(["gnt-instance", "modify"] + alist + [instance["name"]])
542

    
543
  # check no-modify
544
  AssertCommand(["gnt-instance", "modify", instance["name"]], fail=True)
545

    
546
  # Marking offline while instance is running must fail...
547
  AssertCommand(["gnt-instance", "modify", "--offline", instance["name"]],
548
                 fail=True)
549

    
550
  # ...while making it online is ok, and should work
551
  AssertCommand(["gnt-instance", "modify", "--online", instance["name"]])
552

    
553

    
554
@InstanceCheck(INST_DOWN, INST_DOWN, FIRST_ARG)
555
def TestInstanceStoppedModify(instance):
556
  """gnt-instance modify (stopped instance)"""
557
  name = instance["name"]
558

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

    
562
  # Mark instance as offline
563
  AssertCommand(["gnt-instance", "modify", "--offline", name])
564

    
565
  # When the instance is offline shutdown should only work with --force,
566
  # while start should never work
567
  AssertCommand(["gnt-instance", "shutdown", name], fail=True)
568
  AssertCommand(["gnt-instance", "shutdown", "--force", name])
569
  AssertCommand(["gnt-instance", "start", name], fail=True)
570
  AssertCommand(["gnt-instance", "start", "--force", name], fail=True)
571

    
572
  # Also do offline to offline
573
  AssertCommand(["gnt-instance", "modify", "--offline", name])
574

    
575
  # And online again
576
  AssertCommand(["gnt-instance", "modify", "--online", name])
577

    
578

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

    
593

    
594
@InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
595
def TestInstanceModifyDisks(instance):
596
  """gnt-instance modify --disk"""
597
  if not IsDiskSupported(instance):
598
    print qa_utils.FormatInfo("Instance doesn't support disks, skipping test")
599
    return
600

    
601
  size = qa_config.get("disk")[-1]
602
  name = instance["name"]
603
  build_cmd = lambda arg: ["gnt-instance", "modify", "--disk", arg, name]
604
  AssertCommand(build_cmd("add:size=%s" % size))
605
  AssertCommand(build_cmd("remove"))
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
  name = instance["name"]
615
  all_size = qa_config.get("disk")
616
  all_grow = qa_config.get("disk-growth")
617
  if not all_grow:
618
    # missing disk sizes but instance grow disk has been enabled,
619
    # let's set fixed/nomimal growth
620
    all_grow = ["128M" for _ in all_size]
621
  for idx, (size, grow) in enumerate(zip(all_size, all_grow)):
622
    # succeed in grow by amount
623
    AssertCommand(["gnt-instance", "grow-disk", name, str(idx), grow])
624
    # fail in grow to the old size
625
    AssertCommand(["gnt-instance", "grow-disk", "--absolute", name, str(idx),
626
                   size], fail=True)
627
    # succeed to grow to old size + 2 * growth
628
    int_size = utils.ParseUnit(size)
629
    int_grow = utils.ParseUnit(grow)
630
    AssertCommand(["gnt-instance", "grow-disk", "--absolute", name, str(idx),
631
                   str(int_size + 2 * int_grow)])
632

    
633

    
634
def TestInstanceList():
635
  """gnt-instance list"""
636
  qa_utils.GenericQueryTest("gnt-instance", query.INSTANCE_FIELDS.keys())
637

    
638

    
639
def TestInstanceListFields():
640
  """gnt-instance list-fields"""
641
  qa_utils.GenericQueryFieldsTest("gnt-instance", query.INSTANCE_FIELDS.keys())
642

    
643

    
644
@InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
645
def TestInstanceConsole(instance):
646
  """gnt-instance console"""
647
  AssertCommand(["gnt-instance", "console", "--show-cmd", instance["name"]])
648

    
649

    
650
@InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
651
def TestReplaceDisks(instance, curr_nodes, other_nodes):
652
  """gnt-instance replace-disks"""
653
  def buildcmd(args):
654
    cmd = ["gnt-instance", "replace-disks"]
655
    cmd.extend(args)
656
    cmd.append(instance["name"])
657
    return cmd
658

    
659
  if not IsDiskReplacingSupported(instance):
660
    print qa_utils.FormatInfo("Instance doesn't support disk replacing,"
661
                              " skipping test")
662
    return
663

    
664
  # Currently all supported templates have one primary and one secondary node
665
  assert len(curr_nodes) == 2
666
  snode = curr_nodes[1]
667
  assert len(other_nodes) == 1
668
  othernode = other_nodes[0]
669

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

    
687
  AssertCommand(buildcmd(["-a"]))
688
  AssertCommand(["gnt-instance", "stop", instance["name"]])
689
  AssertCommand(buildcmd(["-a"]), fail=True)
690
  AssertCommand(["gnt-instance", "activate-disks", instance["name"]])
691
  AssertCommand(["gnt-instance", "activate-disks", "--wait-for-sync",
692
                 instance["name"]])
693
  AssertCommand(buildcmd(["-a"]))
694
  AssertCommand(["gnt-instance", "start", instance["name"]])
695

    
696

    
697
def _AssertRecreateDisks(cmdargs, instance, fail=False, check=True,
698
                         destroy=True):
699
  """Execute gnt-instance recreate-disks and check the result
700

701
  @param cmdargs: Arguments (instance name excluded)
702
  @param instance: Instance to operate on
703
  @param fail: True if the command is expected to fail
704
  @param check: If True and fail is False, check that the disks work
705
  @prama destroy: If True, destroy the old disks first
706

707
  """
708
  if destroy:
709
    _DestroyInstanceVolumes(instance)
710
  AssertCommand((["gnt-instance", "recreate-disks"] + cmdargs +
711
                 [instance["name"]]), fail)
712
  if not fail and check:
713
    # Quick check that the disks are there
714
    AssertCommand(["gnt-instance", "activate-disks", instance["name"]])
715
    AssertCommand(["gnt-instance", "activate-disks", "--wait-for-sync",
716
                   instance["name"]])
717
    AssertCommand(["gnt-instance", "deactivate-disks", instance["name"]])
718

    
719

    
720
@InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
721
def TestRecreateDisks(instance, inodes, othernodes):
722
  """gnt-instance recreate-disks
723

724
  @param instance: Instance to work on
725
  @param inodes: List of the current nodes of the instance
726
  @param othernodes: list/tuple of nodes where to temporarily recreate disks
727

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

    
764

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

    
772

    
773
@InstanceCheck(None, INST_DOWN, FIRST_ARG)
774
def TestInstanceExportWithRemove(instance, node):
775
  """gnt-backup export --remove-instance"""
776
  AssertCommand(["gnt-backup", "export", "-n", node["primary"],
777
                 "--remove-instance", instance["name"]])
778
  qa_config.ReleaseInstance(instance)
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, force_mac=constants.VALUE_GENERATE))
798
  cmd.append(newinst["name"])
799
  AssertCommand(cmd)
800
  qa_config.SetInstanceTemplate(newinst, templ)
801

    
802

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

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

    
810

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

    
815

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

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

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