qa_instance.py 28.5 KB
Newer Older
1
2
3
#
#

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


"""Instance related QA tests.

"""

26
import operator
27
import os
28
29
import re

30
from ganeti import utils
Michael Hanselmann's avatar
Michael Hanselmann committed
31
from ganeti import constants
32
from ganeti import query
33
from ganeti import pathutils
34
35

import qa_config
Michael Hanselmann's avatar
Michael Hanselmann committed
36
import qa_utils
37
38
import qa_error

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

42
43
44

def _GetDiskStatePath(disk):
  return "/sys/block/%s/device/state" % disk
45
46


47
def _GetGenericAddParameters(inst, disk_template, force_mac=None):
48
49
50
51
52
  params = ["-B"]
  params.append("%s=%s,%s=%s" % (constants.BE_MINMEM,
                                 qa_config.get(constants.BE_MINMEM),
                                 constants.BE_MAXMEM,
                                 qa_config.get(constants.BE_MAXMEM)))
53
54
55
56

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

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

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

67
  return params
Michael Hanselmann's avatar
Michael Hanselmann committed
68
69


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

80
    AssertCommand(cmd, fail=fail)
81

82
    if not fail:
Bernardo Dal Seno's avatar
Bernardo Dal Seno committed
83
84
      _CheckSsconfInstanceList(instance.name)
      instance.SetDiskTemplate(disk_template)
85

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

91
92
  # Handle the case where creation is expected to fail
  assert fail
Bernardo Dal Seno's avatar
Bernardo Dal Seno committed
93
  instance.Release()
94
95
  return None

96

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

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

  """
  node_elem = r"([^,()]+)(?:\s+\([^)]+\))?"
  # re_nodelist matches a list of nodes returned by gnt-instance info, e.g.:
  #  node1.fqdn
  #  node2.fqdn,node3.fqdn
  #  node4.fqdn (group mygroup, group UUID 01234567-abcd-0123-4567-0123456789ab)
  # FIXME This works with no more than 2 secondaries
  re_nodelist = re.compile(node_elem + "(?:," + node_elem + ")?$")
117
118

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

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

151
  assert nodes
152
  assert len(nodes) < 2 or vols
153
154
155
156
157
  return {
    "nodes": nodes,
    "volumes": vols,
    "drbd-minors": drbd_min,
    }
158
159
160
161
162
163
164
165
166
167
168


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

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

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


175
176
def _GetInstanceField(instance, field):
  """Get the value of a field of an instance.
177
178
179
180
181

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

  """
  master = qa_config.GetMasterNode()
  infocmd = utils.ShellQuoteArgs(["gnt-instance", "list", "--no-headers",
187
                                  "--units", "m", "-o", field, instance])
Bernardo Dal Seno's avatar
Bernardo Dal Seno committed
188
  return qa_utils.GetCommandOutput(master.primary, infocmd).strip()
189
190
191
192
193
194
195
196
197
198
199
200
201


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

  @type instance: string
  @param instance: Instance name
  @type field: string
  @param field: Name of the field
  @rtype: bool

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


211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
def _GetNumInstanceField(instance, field):
  """Get a numeric value of a field of an instance.

  @type instance: string
  @param instance: Instance name
  @type field: string
  @param field: Name of the field
  @rtype: int or float

  """
  info_out = _GetInstanceField(instance, field)
  try:
    ret = int(info_out)
  except ValueError:
    try:
      ret = float(info_out)
    except ValueError:
      raise qa_error.Error("Field %s of instance %s has a non-numeric value:"
                           " %s" % (field, instance, info_out))
  return ret


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

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

  """
  specmap = {
    "mem-size": ["be/minmem", "be/maxmem"],
    "cpu-count": ["vcpus"],
    "disk-count": ["disk.count"],
    "disk-size": ["disk.size/ "],
    "nic-count": ["nic.count"],
    }
  # For disks, first we need the number of disks
  if spec == "disk-size":
    (numdisk, _) = GetInstanceSpec(instance, "disk-count")
    fields = ["disk.size/%s" % k for k in range(0, numdisk)]
  else:
    assert spec in specmap, "%s not in %s" % (spec, specmap)
    fields = specmap[spec]
  values = [_GetNumInstanceField(instance, f) for f in fields]
  return (min(values), max(values))


264
def IsFailoverSupported(instance):
265
  return instance.disk_template in constants.DTS_MIRRORED
266
267
268


def IsMigrationSupported(instance):
269
  return instance.disk_template in constants.DTS_MIRRORED
270
271
272


def IsDiskReplacingSupported(instance):
273
  return instance.disk_template == constants.DT_DRBD8
274
275


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


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


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


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


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


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


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


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

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

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

344

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

352
  AssertCommand(["gnt-instance", "reinstall", "-f", instance.name])
Michael Hanselmann's avatar
Michael Hanselmann committed
353

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

Michael Hanselmann's avatar
Michael Hanselmann committed
360

361
362
363
364
365
366
def _ReadSsconfInstanceList():
  """Reads ssconf_instance_list from the master node.

  """
  master = qa_config.GetMasterNode()

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

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

372
  return qa_utils.GetCommandOutput(master.primary,
373
374
375
376
377
378
379
380
381
382
383
384
385
386
                                   utils.ShellQuoteArgs(cmd)).splitlines()


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

  @type instance: string
  @param instance: Instance name

  """
  AssertIn(qa_utils.ResolveInstanceName(instance),
           _ReadSsconfInstanceList())


387
@InstanceCheck(INST_DOWN, INST_DOWN, FIRST_ARG)
Iustin Pop's avatar
Iustin Pop committed
388
389
390
391
392
393
394
def TestInstanceRenameAndBack(rename_source, rename_target):
  """gnt-instance rename

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

  """
395
  _CheckSsconfInstanceList(rename_source)
396

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

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

Iustin Pop's avatar
Iustin Pop committed
412
  # and now rename instance to rename_target...
413
414
  AssertCommand(["gnt-instance", "rename", rename_source, rename_target])
  _CheckSsconfInstanceList(rename_target)
415
  qa_utils.RunInstanceCheck(rename_source, False)
416
  qa_utils.RunInstanceCheck(rename_target, False)
417

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

Iustin Pop's avatar
Iustin Pop committed
425
426
427
  # and back
  AssertCommand(["gnt-instance", "rename", rename_target, rename_source])
  _CheckSsconfInstanceList(rename_source)
428
  qa_utils.RunInstanceCheck(rename_target, False)
429

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

435

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

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

Iustin Pop's avatar
Iustin Pop committed
446
447
  # failover ...
  AssertCommand(cmd)
448
449
  qa_utils.RunInstanceCheck(instance, True)

450
  # ... and back
Iustin Pop's avatar
Iustin Pop committed
451
  AssertCommand(cmd)
452

453

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

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

Iustin Pop's avatar
Iustin Pop committed
467
468
  # migrate ...
  AssertCommand(cmd)
469
  # TODO: Verify the choice between failover and migration
470
471
  qa_utils.RunInstanceCheck(instance, True)

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

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

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

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

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

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


514
515
def TestInstanceInfo(instance):
  """gnt-instance info"""
516
  AssertCommand(["gnt-instance", "info", instance.name])
Michael Hanselmann's avatar
Michael Hanselmann committed
517
518


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

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

528
529
  orig_maxmem = qa_config.get(constants.BE_MAXMEM)
  orig_minmem = qa_config.get(constants.BE_MINMEM)
Iustin Pop's avatar
Iustin Pop committed
530
  #orig_bridge = qa_config.get("bridge", "xen-br0")
531

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

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

    # TODO: bridge tests
    #["--bridge", "xen-br1"],
    #["--bridge", orig_bridge],
549
    ]
550
551
552
553
554
555
556
557
558
559
560
561
562

  if default_hv == constants.HT_XEN_PVM:
    args.extend([
      ["-H", "%s=%s" % (constants.HV_INITRD_PATH, test_initrd)],
      ["-H", "no_%s" % (constants.HV_INITRD_PATH, )],
      ["-H", "%s=%s" % (constants.HV_INITRD_PATH, constants.VALUE_DEFAULT)],
      ])
  elif default_hv == constants.HT_XEN_HVM:
    args.extend([
      ["-H", "%s=acn" % constants.HV_BOOT_ORDER],
      ["-H", "%s=%s" % (constants.HV_BOOT_ORDER, constants.VALUE_DEFAULT)],
      ])

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

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

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

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


577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
@InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
def TestInstanceModifyPrimaryAndBack(instance, currentnode, othernode):
  """gnt-instance modify --new-primary

  This will leave the instance on its original primary node, not other node.

  """
  if instance.disk_template != constants.DT_FILE:
    print qa_utils.FormatInfo("Test only supported for the file disk template")
    return

  name = instance.name
  current = currentnode.primary
  other = othernode.primary

  filestorage = qa_config.get("file-storage-dir")
  disk = os.path.join(filestorage, name)

  AssertCommand(["gnt-instance", "modify", "--new-primary=%s" % other, name],
                fail=True)
  AssertCommand(["gnt-instance", "shutdown", name])
  AssertCommand(["scp", "-r", disk, "%s:%s" % (other, filestorage)])
  AssertCommand(["gnt-instance", "modify", "--new-primary=%s" % other, name])
  AssertCommand(["gnt-instance", "startup", name])

  # and back
  AssertCommand(["gnt-instance", "shutdown", name])
  AssertCommand(["rm", "-rf", disk], node=other)
  AssertCommand(["gnt-instance", "modify", "--new-primary=%s" % current, name])
  AssertCommand(["gnt-instance", "startup", name])


609
@InstanceCheck(INST_DOWN, INST_DOWN, FIRST_ARG)
610
611
def TestInstanceStoppedModify(instance):
  """gnt-instance modify (stopped instance)"""
612
  name = instance.name
613

614
615
  # Instance was not marked offline; try marking it online once more
  AssertCommand(["gnt-instance", "modify", "--online", name])
616
617
618
619

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

620
621
622
623
624
625
626
627
628
629
  # When the instance is offline shutdown should only work with --force,
  # while start should never work
  AssertCommand(["gnt-instance", "shutdown", name], fail=True)
  AssertCommand(["gnt-instance", "shutdown", "--force", name])
  AssertCommand(["gnt-instance", "start", name], fail=True)
  AssertCommand(["gnt-instance", "start", "--force", name], fail=True)

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

630
631
632
  # And online again
  AssertCommand(["gnt-instance", "modify", "--online", name])

633

634
@InstanceCheck(INST_DOWN, INST_DOWN, FIRST_ARG)
635
def TestInstanceConvertDiskToPlain(instance, inodes):
636
  """gnt-instance modify -t"""
637
  name = instance.name
638

639
  template = instance.disk_template
640
  if template != constants.DT_DRBD8:
641
642
643
    print qa_utils.FormatInfo("Unsupported template %s, skipping conversion"
                              " test" % template)
    return
644

645
  assert len(inodes) == 2
646
647
  AssertCommand(["gnt-instance", "modify", "-t", constants.DT_PLAIN, name])
  AssertCommand(["gnt-instance", "modify", "-t", constants.DT_DRBD8,
648
                 "-n", inodes[1].primary, name])
649
650


651
@InstanceCheck(INST_DOWN, INST_DOWN, FIRST_ARG)
Iustin Pop's avatar
Iustin Pop committed
652
653
def TestInstanceGrowDisk(instance):
  """gnt-instance grow-disk"""
654
655
656
  if qa_config.GetExclusiveStorage():
    print qa_utils.FormatInfo("Test not supported with exclusive_storage")
    return
657
658
659
660
661

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

662
  name = instance.name
Iustin Pop's avatar
Iustin Pop committed
663
664
  all_size = qa_config.get("disk")
  all_grow = qa_config.get("disk-growth")
665

Iustin Pop's avatar
Iustin Pop committed
666
667
668
669
  if not all_grow:
    # missing disk sizes but instance grow disk has been enabled,
    # let's set fixed/nomimal growth
    all_grow = ["128M" for _ in all_size]
670

Iustin Pop's avatar
Iustin Pop committed
671
672
673
674
675
676
677
678
679
680
681
682
683
  for idx, (size, grow) in enumerate(zip(all_size, all_grow)):
    # succeed in grow by amount
    AssertCommand(["gnt-instance", "grow-disk", name, str(idx), grow])
    # fail in grow to the old size
    AssertCommand(["gnt-instance", "grow-disk", "--absolute", name, str(idx),
                   size], fail=True)
    # succeed to grow to old size + 2 * growth
    int_size = utils.ParseUnit(size)
    int_grow = utils.ParseUnit(grow)
    AssertCommand(["gnt-instance", "grow-disk", "--absolute", name, str(idx),
                   str(int_size + 2 * int_grow)])


Michael Hanselmann's avatar
Michael Hanselmann committed
684
685
def TestInstanceList():
  """gnt-instance list"""
686
  qa_utils.GenericQueryTest("gnt-instance", query.INSTANCE_FIELDS.keys())
Michael Hanselmann's avatar
Michael Hanselmann committed
687
688


689
690
691
692
693
def TestInstanceListFields():
  """gnt-instance list-fields"""
  qa_utils.GenericQueryFieldsTest("gnt-instance", query.INSTANCE_FIELDS.keys())


694
@InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
695
696
def TestInstanceConsole(instance):
  """gnt-instance console"""
697
  AssertCommand(["gnt-instance", "console", "--show-cmd", instance.name])
698
699


700
@InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
701
def TestReplaceDisks(instance, curr_nodes, other_nodes):
702
703
  """gnt-instance replace-disks"""
  def buildcmd(args):
Iustin Pop's avatar
Iustin Pop committed
704
    cmd = ["gnt-instance", "replace-disks"]
705
    cmd.extend(args)
706
    cmd.append(instance.name)
707
708
    return cmd

709
710
711
712
713
  if not IsDiskReplacingSupported(instance):
    print qa_utils.FormatInfo("Instance doesn't support disk replacing,"
                              " skipping test")
    return

714
715
716
717
718
719
  # Currently all supported templates have one primary and one secondary node
  assert len(curr_nodes) == 2
  snode = curr_nodes[1]
  assert len(other_nodes) == 1
  othernode = other_nodes[0]

720
721
  options = qa_config.get("options", {})
  use_ialloc = options.get("use-iallocators", True)
Iustin Pop's avatar
Iustin Pop committed
722
723
724
  for data in [
    ["-p"],
    ["-s"],
725
726
727
    # A placeholder; the actual command choice depends on use_ialloc
    None,
    # Restore the original secondary
728
    ["--new-secondary=%s" % snode.primary],
Iustin Pop's avatar
Iustin Pop committed
729
    ]:
730
731
732
733
    if data is None:
      if use_ialloc:
        data = ["-I", constants.DEFAULT_IALLOCATOR_SHORTCUT]
      else:
734
        data = ["--new-secondary=%s" % othernode.primary]
Iustin Pop's avatar
Iustin Pop committed
735
    AssertCommand(buildcmd(data))
736

737
  AssertCommand(buildcmd(["-a"]))
738
  AssertCommand(["gnt-instance", "stop", instance.name])
739
  AssertCommand(buildcmd(["-a"]), fail=True)
740
  AssertCommand(["gnt-instance", "activate-disks", instance.name])
741
  AssertCommand(["gnt-instance", "activate-disks", "--wait-for-sync",
742
                 instance.name])
743
  AssertCommand(buildcmd(["-a"]))
744
  AssertCommand(["gnt-instance", "start", instance.name])
745

746

747
748
749
750
751
752
753
754
755
756
757
758
759
760
def _AssertRecreateDisks(cmdargs, instance, fail=False, check=True,
                         destroy=True):
  """Execute gnt-instance recreate-disks and check the result

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

  """
  if destroy:
    _DestroyInstanceVolumes(instance)
  AssertCommand((["gnt-instance", "recreate-disks"] + cmdargs +
761
                 [instance.name]), fail)
762
763
  if not fail and check:
    # Quick check that the disks are there
764
    AssertCommand(["gnt-instance", "activate-disks", instance.name])
765
    AssertCommand(["gnt-instance", "activate-disks", "--wait-for-sync",
766
767
                   instance.name])
    AssertCommand(["gnt-instance", "deactivate-disks", instance.name])
768

Bernardo Dal Seno's avatar
Bernardo Dal Seno committed
769

770
@InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
771
def TestRecreateDisks(instance, inodes, othernodes):
772
773
774
  """gnt-instance recreate-disks

  @param instance: Instance to work on
775
  @param inodes: List of the current nodes of the instance
776
777
778
  @param othernodes: list/tuple of nodes where to temporarily recreate disks

  """
779
780
  options = qa_config.get("options", {})
  use_ialloc = options.get("use-iallocators", True)
781
782
  other_seq = ":".join([n.primary for n in othernodes])
  orig_seq = ":".join([n.primary for n in inodes])
783
  # These fail because the instance is running
784
  _AssertRecreateDisks(["-n", other_seq], instance, fail=True, destroy=False)
785
786
787
788
  if use_ialloc:
    _AssertRecreateDisks(["-I", "hail"], instance, fail=True, destroy=False)
  else:
    _AssertRecreateDisks(["-n", other_seq], instance, fail=True, destroy=False)
789
  AssertCommand(["gnt-instance", "stop", instance.name])
790
791
792
793
794
  # Disks exist: this should fail
  _AssertRecreateDisks([], instance, fail=True, destroy=False)
  # Recreate disks in place
  _AssertRecreateDisks([], instance)
  # Move disks away
795
796
797
798
799
800
801
  if use_ialloc:
    _AssertRecreateDisks(["-I", "hail"], instance)
    # Move disks somewhere else
    _AssertRecreateDisks(["-I", constants.DEFAULT_IALLOCATOR_SHORTCUT],
                         instance)
  else:
    _AssertRecreateDisks(["-n", other_seq], instance)
802
803
804
  # Move disks back
  _AssertRecreateDisks(["-n", orig_seq], instance, check=False)
  # This and InstanceCheck decoration check that the disks are working
805
806
  AssertCommand(["gnt-instance", "reinstall", "-f", instance.name])
  AssertCommand(["gnt-instance", "start", instance.name])
807
808


809
@InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
Michael Hanselmann's avatar
Michael Hanselmann committed
810
def TestInstanceExport(instance, node):
811
  """gnt-backup export -n ..."""
812
  name = instance.name
813
  AssertCommand(["gnt-backup", "export", "-n", node.primary, name])
Iustin Pop's avatar
Iustin Pop committed
814
  return qa_utils.ResolveInstanceName(name)
Michael Hanselmann's avatar
Michael Hanselmann committed
815
816


817
@InstanceCheck(None, INST_DOWN, FIRST_ARG)
818
819
def TestInstanceExportWithRemove(instance, node):
  """gnt-backup export --remove-instance"""
820
  AssertCommand(["gnt-backup", "export", "-n", node.primary,
821
                 "--remove-instance", instance.name])
822
823


824
@InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
825
826
def TestInstanceExportNoTarget(instance):
  """gnt-backup export (without target node, should fail)"""
827
  AssertCommand(["gnt-backup", "export", instance.name], fail=True)
828
829


830
@InstanceCheck(None, INST_DOWN, FIRST_ARG)
831
def TestInstanceImport(newinst, node, expnode, name):
Michael Hanselmann's avatar
Michael Hanselmann committed
832
  """gnt-backup import"""
833
  templ = constants.DT_PLAIN
Iustin Pop's avatar
Iustin Pop committed
834
  cmd = (["gnt-backup", "import",
835
          "--disk-template=%s" % templ,
Iustin Pop's avatar
Iustin Pop committed
836
          "--no-ip-check",
837
          "--src-node=%s" % expnode.primary,
838
          "--src-dir=%s/%s" % (pathutils.EXPORT_DIR, name),
839
          "--node=%s" % node.primary] +
840
841
         _GetGenericAddParameters(newinst, templ,
                                  force_mac=constants.VALUE_GENERATE))
842
  cmd.append(newinst.name)
Iustin Pop's avatar
Iustin Pop committed
843
  AssertCommand(cmd)
844
  newinst.SetDiskTemplate(templ)
Michael Hanselmann's avatar
Michael Hanselmann committed
845
846
847
848


def TestBackupList(expnode):
  """gnt-backup list"""
849
  AssertCommand(["gnt-backup", "list", "--node=%s" % expnode.primary])
850

851
852
853
854
855
856
857
  qa_utils.GenericQueryTest("gnt-backup", query.EXPORT_FIELDS.keys(),
                            namefield=None, test_unknown=False)


def TestBackupListFields():
  """gnt-backup list-fields"""
  qa_utils.GenericQueryFieldsTest("gnt-backup", query.EXPORT_FIELDS.keys())
858
859
860
861
862
863
864
865
866
867
868


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

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

  """
869
  info = _GetInstanceInfo(instance.name)
870
871
872
873
874
875
  set_offline(snode)
  try:
    TestInstanceRemove(instance)
  finally:
    set_online(snode)
  # Clean up the disks on the offline node
876
  for minor in info["drbd-minors"][snode.primary]:
877
878
    AssertCommand(["drbdsetup", str(minor), "down"], node=snode)
  AssertCommand(["lvremove", "-f"] + info["volumes"], node=snode)