Statistics
| Branch: | Tag: | Revision:

root / qa / qa_instance.py @ 0b85e1bd

History | View | Annotate | Download (29.7 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 os
28
import re
29

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

    
35
import qa_config
36
import qa_utils
37
import qa_error
38

    
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
def _GetDiskStatePath(disk):
44
  return "/sys/block/%s/device/state" % disk
45

    
46

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

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

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

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

    
67
  return params
68

    
69

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

    
80
    AssertCommand(cmd, fail=fail)
81

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

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

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

    
96

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

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

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

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

    
134
  disk_template = info["Disk template"]
135
  if not disk_template:
136
    raise qa_error.Error("Can't get instance disk template")
137
  storage_type = constants.DISK_TEMPLATES_STORAGE_TYPE[disk_template]
138

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

    
158
  assert nodes
159
  assert len(nodes) < 2 or vols
160
  return {
161
    "nodes": nodes,
162
    "volumes": vols,
163
    "drbd-minors": drbd_min,
164
    "disk-template": disk_template,
165
    "storage-type": storage_type,
166
    }
167

    
168

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

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

177
  """
178
  info = _GetInstanceInfo(instance.name)
179
  vols = info["volumes"]
180
  for node in info["nodes"]:
181
    AssertCommand(["lvremove", "-f"] + vols, node=node)
182

    
183

    
184
def _GetInstanceField(instance, field):
185
  """Get the value of a field of an instance.
186

187
  @type instance: string
188
  @param instance: Instance name
189
  @type field: string
190
  @param field: Name of the field
191
  @rtype: string
192

193
  """
194
  master = qa_config.GetMasterNode()
195
  infocmd = utils.ShellQuoteArgs(["gnt-instance", "list", "--no-headers",
196
                                  "--units", "m", "-o", field, instance])
197
  return qa_utils.GetCommandOutput(master.primary, infocmd).strip()
198

    
199

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

203
  @type instance: string
204
  @param instance: Instance name
205
  @type field: string
206
  @param field: Name of the field
207
  @rtype: bool
208

209
  """
210
  info_out = _GetInstanceField(instance, field)
211
  if info_out == "Y":
212
    return True
213
  elif info_out == "N":
214
    return False
215
  else:
216
    raise qa_error.Error("Field %s of instance %s has a non-Boolean value:"
217
                         " %s" % (field, instance, info_out))
218

    
219

    
220
def _GetNumInstanceField(instance, field):
221
  """Get a numeric value of a field of an instance.
222

223
  @type instance: string
224
  @param instance: Instance name
225
  @type field: string
226
  @param field: Name of the field
227
  @rtype: int or float
228

229
  """
230
  info_out = _GetInstanceField(instance, field)
231
  try:
232
    ret = int(info_out)
233
  except ValueError:
234
    try:
235
      ret = float(info_out)
236
    except ValueError:
237
      raise qa_error.Error("Field %s of instance %s has a non-numeric value:"
238
                           " %s" % (field, instance, info_out))
239
  return ret
240

    
241

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

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

254
  """
255
  specmap = {
256
    "mem-size": ["be/minmem", "be/maxmem"],
257
    "cpu-count": ["vcpus"],
258
    "disk-count": ["disk.count"],
259
    "disk-size": ["disk.size/ "],
260
    "nic-count": ["nic.count"],
261
    }
262
  # For disks, first we need the number of disks
263
  if spec == "disk-size":
264
    (numdisk, _) = GetInstanceSpec(instance, "disk-count")
265
    fields = ["disk.size/%s" % k for k in range(0, numdisk)]
266
  else:
267
    assert spec in specmap, "%s not in %s" % (spec, specmap)
268
    fields = specmap[spec]
269
  values = [_GetNumInstanceField(instance, f) for f in fields]
270
  return (min(values), max(values))
271

    
272

    
273
def IsFailoverSupported(instance):
274
  return instance.disk_template in constants.DTS_MIRRORED
275

    
276

    
277
def IsMigrationSupported(instance):
278
  return instance.disk_template in constants.DTS_MIRRORED
279

    
280

    
281
def IsDiskReplacingSupported(instance):
282
  return instance.disk_template == constants.DT_DRBD8
283

    
284

    
285
def TestInstanceAddWithPlainDisk(nodes, fail=False):
286
  """gnt-instance add -t plain"""
287
  assert len(nodes) == 1
288
  instance = _DiskTest(nodes[0].primary, constants.DT_PLAIN, fail=fail)
289
  if not fail:
290
    qa_utils.RunInstanceCheck(instance, True)
291
  return instance
292

    
293

    
294
@InstanceCheck(None, INST_UP, RETURN_VALUE)
295
def TestInstanceAddWithDrbdDisk(nodes):
296
  """gnt-instance add -t drbd"""
297
  assert len(nodes) == 2
298
  return _DiskTest(":".join(map(operator.attrgetter("primary"), nodes)),
299
                   constants.DT_DRBD8)
300

    
301

    
302
@InstanceCheck(None, INST_UP, RETURN_VALUE)
303
def TestInstanceAddFile(nodes):
304
  """gnt-instance add -t file"""
305
  assert len(nodes) == 1
306
  return _DiskTest(nodes[0].primary, constants.DT_FILE)
307

    
308

    
309
@InstanceCheck(None, INST_UP, RETURN_VALUE)
310
def TestInstanceAddDiskless(nodes):
311
  """gnt-instance add -t diskless"""
312
  assert len(nodes) == 1
313
  return _DiskTest(nodes[0].primary, constants.DT_DISKLESS)
314

    
315

    
316
@InstanceCheck(None, INST_DOWN, FIRST_ARG)
317
def TestInstanceRemove(instance):
318
  """gnt-instance remove"""
319
  AssertCommand(["gnt-instance", "remove", "-f", instance.name])
320

    
321

    
322
@InstanceCheck(INST_DOWN, INST_UP, FIRST_ARG)
323
def TestInstanceStartup(instance):
324
  """gnt-instance startup"""
325
  AssertCommand(["gnt-instance", "startup", instance.name])
326

    
327

    
328
@InstanceCheck(INST_UP, INST_DOWN, FIRST_ARG)
329
def TestInstanceShutdown(instance):
330
  """gnt-instance shutdown"""
331
  AssertCommand(["gnt-instance", "shutdown", instance.name])
332

    
333

    
334
@InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
335
def TestInstanceReboot(instance):
336
  """gnt-instance reboot"""
337
  options = qa_config.get("options", {})
338
  reboot_types = options.get("reboot-types", constants.REBOOT_TYPES)
339
  name = instance.name
340
  for rtype in reboot_types:
341
    AssertCommand(["gnt-instance", "reboot", "--type=%s" % rtype, name])
342

    
343
  AssertCommand(["gnt-instance", "shutdown", name])
344
  qa_utils.RunInstanceCheck(instance, False)
345
  AssertCommand(["gnt-instance", "reboot", name])
346

    
347
  master = qa_config.GetMasterNode()
348
  cmd = ["gnt-instance", "list", "--no-headers", "-o", "status", name]
349
  result_output = qa_utils.GetCommandOutput(master.primary,
350
                                            utils.ShellQuoteArgs(cmd))
351
  AssertEqual(result_output.strip(), constants.INSTST_RUNNING)
352

    
353

    
354
@InstanceCheck(INST_DOWN, INST_DOWN, FIRST_ARG)
355
def TestInstanceReinstall(instance):
356
  """gnt-instance reinstall"""
357
  if instance.disk_template == constants.DT_DISKLESS:
358
    print qa_utils.FormatInfo("Test not supported for diskless instances")
359
    return
360

    
361
  AssertCommand(["gnt-instance", "reinstall", "-f", instance.name])
362

    
363
  # Test with non-existant OS definition
364
  AssertCommand(["gnt-instance", "reinstall", "-f",
365
                 "--os-type=NonExistantOsForQa",
366
                 instance.name],
367
                fail=True)
368

    
369

    
370
def _ReadSsconfInstanceList():
371
  """Reads ssconf_instance_list from the master node.
372

373
  """
374
  master = qa_config.GetMasterNode()
375

    
376
  ssconf_path = utils.PathJoin(pathutils.DATA_DIR,
377
                               "ssconf_%s" % constants.SS_INSTANCE_LIST)
378

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

    
381
  return qa_utils.GetCommandOutput(master.primary,
382
                                   utils.ShellQuoteArgs(cmd)).splitlines()
383

    
384

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

388
  @type instance: string
389
  @param instance: Instance name
390

391
  """
392
  AssertIn(qa_utils.ResolveInstanceName(instance),
393
           _ReadSsconfInstanceList())
394

    
395

    
396
@InstanceCheck(INST_DOWN, INST_DOWN, FIRST_ARG)
397
def TestInstanceRenameAndBack(rename_source, rename_target):
398
  """gnt-instance rename
399

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

403
  """
404
  _CheckSsconfInstanceList(rename_source)
405

    
406
  # first do a rename to a different actual name, expecting it to fail
407
  qa_utils.AddToEtcHosts(["meeeeh-not-exists", rename_target])
408
  try:
409
    AssertCommand(["gnt-instance", "rename", rename_source, rename_target],
410
                  fail=True)
411
    _CheckSsconfInstanceList(rename_source)
412
  finally:
413
    qa_utils.RemoveFromEtcHosts(["meeeeh-not-exists", rename_target])
414

    
415
  info = _GetInstanceInfo(rename_source)
416

    
417
  # Check instance volume tags correctly updated. Note that this check is lvm
418
  # specific, so we skip it for non-lvm-based instances.
419
  # FIXME: This will need updating when instances will be able to have
420
  # different disks living on storage pools with etherogeneous storage types.
421
  # FIXME: This check should be put inside the disk/storage class themselves,
422
  # rather than explicitly called here.
423
  if info["storage-type"] == constants.ST_LVM_VG:
424
    # In the lvm world we can check for tags on the logical volume
425
    tags_cmd = ("lvs -o tags --noheadings %s | grep " %
426
                (" ".join(info["volumes"]), ))
427
  else:
428
    # Other storage types don't have tags, so we use an always failing command,
429
    # to make sure it never gets executed
430
    tags_cmd = "false"
431

    
432
  # and now rename instance to rename_target...
433
  AssertCommand(["gnt-instance", "rename", rename_source, rename_target])
434
  _CheckSsconfInstanceList(rename_target)
435
  qa_utils.RunInstanceCheck(rename_source, False)
436
  qa_utils.RunInstanceCheck(rename_target, False)
437

    
438
  # NOTE: tags might not be the exactly as the instance name, due to
439
  # charset restrictions; hence the test might be flaky
440
  if (rename_source != rename_target and
441
      info["storage-type"] == constants.ST_LVM_VG):
442
    for node in info["nodes"]:
443
      AssertCommand(tags_cmd + rename_source, node=node, fail=True)
444
      AssertCommand(tags_cmd + rename_target, node=node, fail=False)
445

    
446
  # and back
447
  AssertCommand(["gnt-instance", "rename", rename_target, rename_source])
448
  _CheckSsconfInstanceList(rename_source)
449
  qa_utils.RunInstanceCheck(rename_target, False)
450

    
451
  if (rename_source != rename_target and
452
      info["storage-type"] == constants.ST_LVM_VG):
453
    for node in info["nodes"]:
454
      AssertCommand(tags_cmd + rename_source, node=node, fail=False)
455
      AssertCommand(tags_cmd + rename_target, node=node, fail=True)
456

    
457

    
458
@InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
459
def TestInstanceFailover(instance):
460
  """gnt-instance failover"""
461
  if not IsFailoverSupported(instance):
462
    print qa_utils.FormatInfo("Instance doesn't support failover, skipping"
463
                              " test")
464
    return
465

    
466
  cmd = ["gnt-instance", "failover", "--force", instance.name]
467

    
468
  # failover ...
469
  AssertCommand(cmd)
470
  qa_utils.RunInstanceCheck(instance, True)
471

    
472
  # ... and back
473
  AssertCommand(cmd)
474

    
475

    
476
@InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
477
def TestInstanceMigrate(instance, toggle_always_failover=True):
478
  """gnt-instance migrate"""
479
  if not IsMigrationSupported(instance):
480
    print qa_utils.FormatInfo("Instance doesn't support migration, skipping"
481
                              " test")
482
    return
483

    
484
  cmd = ["gnt-instance", "migrate", "--force", instance.name]
485
  af_par = constants.BE_ALWAYS_FAILOVER
486
  af_field = "be/" + constants.BE_ALWAYS_FAILOVER
487
  af_init_val = _GetBoolInstanceField(instance.name, af_field)
488

    
489
  # migrate ...
490
  AssertCommand(cmd)
491
  # TODO: Verify the choice between failover and migration
492
  qa_utils.RunInstanceCheck(instance, True)
493

    
494
  # ... and back (possibly with always_failover toggled)
495
  if toggle_always_failover:
496
    AssertCommand(["gnt-instance", "modify", "-B",
497
                   ("%s=%s" % (af_par, not af_init_val)),
498
                   instance.name])
499
  AssertCommand(cmd)
500
  # TODO: Verify the choice between failover and migration
501
  qa_utils.RunInstanceCheck(instance, True)
502
  if toggle_always_failover:
503
    AssertCommand(["gnt-instance", "modify", "-B",
504
                   ("%s=%s" % (af_par, af_init_val)), instance.name])
505

    
506
  # TODO: Split into multiple tests
507
  AssertCommand(["gnt-instance", "shutdown", instance.name])
508
  qa_utils.RunInstanceCheck(instance, False)
509
  AssertCommand(cmd, fail=True)
510
  AssertCommand(["gnt-instance", "migrate", "--force", "--allow-failover",
511
                 instance.name])
512
  AssertCommand(["gnt-instance", "start", instance.name])
513
  AssertCommand(cmd)
514
  # @InstanceCheck enforces the check that the instance is running
515
  qa_utils.RunInstanceCheck(instance, True)
516

    
517
  AssertCommand(["gnt-instance", "modify", "-B",
518
                 ("%s=%s" %
519
                  (constants.BE_ALWAYS_FAILOVER, constants.VALUE_TRUE)),
520
                 instance.name])
521

    
522
  AssertCommand(cmd)
523
  qa_utils.RunInstanceCheck(instance, True)
524
  # TODO: Verify that a failover has been done instead of a migration
525

    
526
  # TODO: Verify whether the default value is restored here (not hardcoded)
527
  AssertCommand(["gnt-instance", "modify", "-B",
528
                 ("%s=%s" %
529
                  (constants.BE_ALWAYS_FAILOVER, constants.VALUE_FALSE)),
530
                 instance.name])
531

    
532
  AssertCommand(cmd)
533
  qa_utils.RunInstanceCheck(instance, True)
534

    
535

    
536
def TestInstanceInfo(instance):
537
  """gnt-instance info"""
538
  AssertCommand(["gnt-instance", "info", instance.name])
539

    
540

    
541
@InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
542
def TestInstanceModify(instance):
543
  """gnt-instance modify"""
544
  default_hv = qa_config.GetDefaultHypervisor()
545

    
546
  # Assume /sbin/init exists on all systems
547
  test_kernel = "/sbin/init"
548
  test_initrd = test_kernel
549

    
550
  orig_maxmem = qa_config.get(constants.BE_MAXMEM)
551
  orig_minmem = qa_config.get(constants.BE_MINMEM)
552
  #orig_bridge = qa_config.get("bridge", "xen-br0")
553

    
554
  args = [
555
    ["-B", "%s=128" % constants.BE_MINMEM],
556
    ["-B", "%s=128" % constants.BE_MAXMEM],
557
    ["-B", "%s=%s,%s=%s" % (constants.BE_MINMEM, orig_minmem,
558
                            constants.BE_MAXMEM, orig_maxmem)],
559
    ["-B", "%s=2" % constants.BE_VCPUS],
560
    ["-B", "%s=1" % constants.BE_VCPUS],
561
    ["-B", "%s=%s" % (constants.BE_VCPUS, constants.VALUE_DEFAULT)],
562
    ["-B", "%s=%s" % (constants.BE_ALWAYS_FAILOVER, constants.VALUE_TRUE)],
563
    ["-B", "%s=%s" % (constants.BE_ALWAYS_FAILOVER, constants.VALUE_DEFAULT)],
564

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

    
568
    # TODO: bridge tests
569
    #["--bridge", "xen-br1"],
570
    #["--bridge", orig_bridge],
571
    ]
572

    
573
  if default_hv == constants.HT_XEN_PVM:
574
    args.extend([
575
      ["-H", "%s=%s" % (constants.HV_INITRD_PATH, test_initrd)],
576
      ["-H", "no_%s" % (constants.HV_INITRD_PATH, )],
577
      ["-H", "%s=%s" % (constants.HV_INITRD_PATH, constants.VALUE_DEFAULT)],
578
      ])
579
  elif default_hv == constants.HT_XEN_HVM:
580
    args.extend([
581
      ["-H", "%s=acn" % constants.HV_BOOT_ORDER],
582
      ["-H", "%s=%s" % (constants.HV_BOOT_ORDER, constants.VALUE_DEFAULT)],
583
      ])
584

    
585
  for alist in args:
586
    AssertCommand(["gnt-instance", "modify"] + alist + [instance.name])
587

    
588
  # check no-modify
589
  AssertCommand(["gnt-instance", "modify", instance.name], fail=True)
590

    
591
  # Marking offline while instance is running must fail...
592
  AssertCommand(["gnt-instance", "modify", "--offline", instance.name],
593
                 fail=True)
594

    
595
  # ...while making it online is ok, and should work
596
  AssertCommand(["gnt-instance", "modify", "--online", instance.name])
597

    
598

    
599
@InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
600
def TestInstanceModifyPrimaryAndBack(instance, currentnode, othernode):
601
  """gnt-instance modify --new-primary
602

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

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

    
610
  name = instance.name
611
  current = currentnode.primary
612
  other = othernode.primary
613

    
614
  # FIXME: the qa doesn't have a customizable file storage dir parameter. As
615
  # such for now we use the default.
616
  filestorage = pathutils.DEFAULT_FILE_STORAGE_DIR
617
  disk = os.path.join(filestorage, name)
618

    
619
  AssertCommand(["gnt-instance", "modify", "--new-primary=%s" % other, name],
620
                fail=True)
621
  AssertCommand(["gnt-instance", "shutdown", name])
622
  AssertCommand(["scp", "-r", disk, "%s:%s" % (other, filestorage)])
623
  AssertCommand(["gnt-instance", "modify", "--new-primary=%s" % other, name])
624
  AssertCommand(["gnt-instance", "startup", name])
625

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

    
632

    
633
@InstanceCheck(INST_DOWN, INST_DOWN, FIRST_ARG)
634
def TestInstanceStoppedModify(instance):
635
  """gnt-instance modify (stopped instance)"""
636
  name = instance.name
637

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

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

    
644
  # When the instance is offline shutdown should only work with --force,
645
  # while start should never work
646
  AssertCommand(["gnt-instance", "shutdown", name], fail=True)
647
  AssertCommand(["gnt-instance", "shutdown", "--force", name])
648
  AssertCommand(["gnt-instance", "start", name], fail=True)
649
  AssertCommand(["gnt-instance", "start", "--force", name], fail=True)
650

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

    
654
  # And online again
655
  AssertCommand(["gnt-instance", "modify", "--online", name])
656

    
657

    
658
@InstanceCheck(INST_DOWN, INST_DOWN, FIRST_ARG)
659
def TestInstanceConvertDiskToPlain(instance, inodes):
660
  """gnt-instance modify -t"""
661
  name = instance.name
662

    
663
  template = instance.disk_template
664
  if template != constants.DT_DRBD8:
665
    print qa_utils.FormatInfo("Unsupported template %s, skipping conversion"
666
                              " test" % template)
667
    return
668

    
669
  assert len(inodes) == 2
670
  AssertCommand(["gnt-instance", "modify", "-t", constants.DT_PLAIN, name])
671
  AssertCommand(["gnt-instance", "modify", "-t", constants.DT_DRBD8,
672
                 "-n", inodes[1].primary, name])
673

    
674

    
675
@InstanceCheck(INST_DOWN, INST_DOWN, FIRST_ARG)
676
def TestInstanceGrowDisk(instance):
677
  """gnt-instance grow-disk"""
678
  if qa_config.GetExclusiveStorage():
679
    print qa_utils.FormatInfo("Test not supported with exclusive_storage")
680
    return
681

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

    
686
  name = instance.name
687
  all_size = qa_config.get("disk")
688
  all_grow = qa_config.get("disk-growth")
689

    
690
  if not all_grow:
691
    # missing disk sizes but instance grow disk has been enabled,
692
    # let's set fixed/nomimal growth
693
    all_grow = ["128M" for _ in all_size]
694

    
695
  for idx, (size, grow) in enumerate(zip(all_size, all_grow)):
696
    # succeed in grow by amount
697
    AssertCommand(["gnt-instance", "grow-disk", name, str(idx), grow])
698
    # fail in grow to the old size
699
    AssertCommand(["gnt-instance", "grow-disk", "--absolute", name, str(idx),
700
                   size], fail=True)
701
    # succeed to grow to old size + 2 * growth
702
    int_size = utils.ParseUnit(size)
703
    int_grow = utils.ParseUnit(grow)
704
    AssertCommand(["gnt-instance", "grow-disk", "--absolute", name, str(idx),
705
                   str(int_size + 2 * int_grow)])
706

    
707

    
708
def TestInstanceList():
709
  """gnt-instance list"""
710
  qa_utils.GenericQueryTest("gnt-instance", query.INSTANCE_FIELDS.keys())
711

    
712

    
713
def TestInstanceListFields():
714
  """gnt-instance list-fields"""
715
  qa_utils.GenericQueryFieldsTest("gnt-instance", query.INSTANCE_FIELDS.keys())
716

    
717

    
718
@InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
719
def TestInstanceConsole(instance):
720
  """gnt-instance console"""
721
  AssertCommand(["gnt-instance", "console", "--show-cmd", instance.name])
722

    
723

    
724
@InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
725
def TestReplaceDisks(instance, curr_nodes, other_nodes):
726
  """gnt-instance replace-disks"""
727
  def buildcmd(args):
728
    cmd = ["gnt-instance", "replace-disks"]
729
    cmd.extend(args)
730
    cmd.append(instance.name)
731
    return cmd
732

    
733
  if not IsDiskReplacingSupported(instance):
734
    print qa_utils.FormatInfo("Instance doesn't support disk replacing,"
735
                              " skipping test")
736
    return
737

    
738
  # Currently all supported templates have one primary and one secondary node
739
  assert len(curr_nodes) == 2
740
  snode = curr_nodes[1]
741
  assert len(other_nodes) == 1
742
  othernode = other_nodes[0]
743

    
744
  options = qa_config.get("options", {})
745
  use_ialloc = options.get("use-iallocators", True)
746
  for data in [
747
    ["-p"],
748
    ["-s"],
749
    # A placeholder; the actual command choice depends on use_ialloc
750
    None,
751
    # Restore the original secondary
752
    ["--new-secondary=%s" % snode.primary],
753
    ]:
754
    if data is None:
755
      if use_ialloc:
756
        data = ["-I", constants.DEFAULT_IALLOCATOR_SHORTCUT]
757
      else:
758
        data = ["--new-secondary=%s" % othernode.primary]
759
    AssertCommand(buildcmd(data))
760

    
761
  AssertCommand(buildcmd(["-a"]))
762
  AssertCommand(["gnt-instance", "stop", instance.name])
763
  AssertCommand(buildcmd(["-a"]), fail=True)
764
  AssertCommand(["gnt-instance", "activate-disks", instance.name])
765
  AssertCommand(["gnt-instance", "activate-disks", "--wait-for-sync",
766
                 instance.name])
767
  AssertCommand(buildcmd(["-a"]))
768
  AssertCommand(["gnt-instance", "start", instance.name])
769

    
770

    
771
def _AssertRecreateDisks(cmdargs, instance, fail=False, check=True,
772
                         destroy=True):
773
  """Execute gnt-instance recreate-disks and check the result
774

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

781
  """
782
  if destroy:
783
    _DestroyInstanceVolumes(instance)
784
  AssertCommand((["gnt-instance", "recreate-disks"] + cmdargs +
785
                 [instance.name]), fail)
786
  if not fail and check:
787
    # Quick check that the disks are there
788
    AssertCommand(["gnt-instance", "activate-disks", instance.name])
789
    AssertCommand(["gnt-instance", "activate-disks", "--wait-for-sync",
790
                   instance.name])
791
    AssertCommand(["gnt-instance", "deactivate-disks", instance.name])
792

    
793

    
794
@InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
795
def TestRecreateDisks(instance, inodes, othernodes):
796
  """gnt-instance recreate-disks
797

798
  @param instance: Instance to work on
799
  @param inodes: List of the current nodes of the instance
800
  @param othernodes: list/tuple of nodes where to temporarily recreate disks
801

802
  """
803
  options = qa_config.get("options", {})
804
  use_ialloc = options.get("use-iallocators", True)
805
  other_seq = ":".join([n.primary for n in othernodes])
806
  orig_seq = ":".join([n.primary for n in inodes])
807
  # These fail because the instance is running
808
  _AssertRecreateDisks(["-n", other_seq], instance, fail=True, destroy=False)
809
  if use_ialloc:
810
    _AssertRecreateDisks(["-I", "hail"], instance, fail=True, destroy=False)
811
  else:
812
    _AssertRecreateDisks(["-n", other_seq], instance, fail=True, destroy=False)
813
  AssertCommand(["gnt-instance", "stop", instance.name])
814
  # Disks exist: this should fail
815
  _AssertRecreateDisks([], instance, fail=True, destroy=False)
816
  # Recreate disks in place
817
  _AssertRecreateDisks([], instance)
818
  # Move disks away
819
  if use_ialloc:
820
    _AssertRecreateDisks(["-I", "hail"], instance)
821
    # Move disks somewhere else
822
    _AssertRecreateDisks(["-I", constants.DEFAULT_IALLOCATOR_SHORTCUT],
823
                         instance)
824
  else:
825
    _AssertRecreateDisks(["-n", other_seq], instance)
826
  # Move disks back
827
  _AssertRecreateDisks(["-n", orig_seq], instance, check=False)
828
  # This and InstanceCheck decoration check that the disks are working
829
  AssertCommand(["gnt-instance", "reinstall", "-f", instance.name])
830
  AssertCommand(["gnt-instance", "start", instance.name])
831

    
832

    
833
@InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
834
def TestInstanceExport(instance, node):
835
  """gnt-backup export -n ..."""
836
  name = instance.name
837
  AssertCommand(["gnt-backup", "export", "-n", node.primary, name])
838
  return qa_utils.ResolveInstanceName(name)
839

    
840

    
841
@InstanceCheck(None, INST_DOWN, FIRST_ARG)
842
def TestInstanceExportWithRemove(instance, node):
843
  """gnt-backup export --remove-instance"""
844
  AssertCommand(["gnt-backup", "export", "-n", node.primary,
845
                 "--remove-instance", instance.name])
846

    
847

    
848
@InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
849
def TestInstanceExportNoTarget(instance):
850
  """gnt-backup export (without target node, should fail)"""
851
  AssertCommand(["gnt-backup", "export", instance.name], fail=True)
852

    
853

    
854
@InstanceCheck(None, INST_DOWN, FIRST_ARG)
855
def TestInstanceImport(newinst, node, expnode, name):
856
  """gnt-backup import"""
857
  templ = constants.DT_PLAIN
858
  cmd = (["gnt-backup", "import",
859
          "--disk-template=%s" % templ,
860
          "--no-ip-check",
861
          "--src-node=%s" % expnode.primary,
862
          "--src-dir=%s/%s" % (pathutils.EXPORT_DIR, name),
863
          "--node=%s" % node.primary] +
864
         _GetGenericAddParameters(newinst, templ,
865
                                  force_mac=constants.VALUE_GENERATE))
866
  cmd.append(newinst.name)
867
  AssertCommand(cmd)
868
  newinst.SetDiskTemplate(templ)
869

    
870

    
871
def TestBackupList(expnode):
872
  """gnt-backup list"""
873
  AssertCommand(["gnt-backup", "list", "--node=%s" % expnode.primary])
874

    
875
  qa_utils.GenericQueryTest("gnt-backup", query.EXPORT_FIELDS.keys(),
876
                            namefield=None, test_unknown=False)
877

    
878

    
879
def TestBackupListFields():
880
  """gnt-backup list-fields"""
881
  qa_utils.GenericQueryFieldsTest("gnt-backup", query.EXPORT_FIELDS.keys())
882

    
883

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

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

892
  """
893
  info = _GetInstanceInfo(instance.name)
894
  set_offline(snode)
895
  try:
896
    TestInstanceRemove(instance)
897
  finally:
898
    set_online(snode)
899
  # Clean up the disks on the offline node
900
  for minor in info["drbd-minors"][snode.primary]:
901
    AssertCommand(["drbdsetup", str(minor), "down"], node=snode)
902
  AssertCommand(["lvremove", "-f"] + info["volumes"], node=snode)