Statistics
| Branch: | Tag: | Revision:

root / qa / qa_instance.py @ e61c0f24

History | View | Annotate | Download (39.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 os
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 AssertCommand, AssertEqual
39
from qa_utils import InstanceCheck, INST_DOWN, INST_UP, FIRST_ARG, RETURN_VALUE
40
from qa_instance_utils import CheckSsconfInstanceList, \
41
                              CreateInstanceDrbd8, \
42
                              CreateInstanceByDiskTemplate, \
43
                              CreateInstanceByDiskTemplateOneNode, \
44
                              GetGenericAddParameters
45

    
46

    
47
def _GetDiskStatePath(disk):
48
  return "/sys/block/%s/device/state" % disk
49

    
50

    
51
def GetInstanceInfo(instance):
52
  """Return information about the actual state of an instance.
53

54
  @type instance: string
55
  @param instance: the instance name
56
  @return: a dictionary with the following keys:
57
      - "nodes": instance nodes, a list of strings
58
      - "volumes": instance volume IDs, a list of strings
59
      - "drbd-minors": DRBD minors used by the instance, a dictionary where
60
        keys are nodes, and values are lists of integers (or an empty
61
        dictionary for non-DRBD instances)
62
      - "disk-template": instance disk template
63
      - "storage-type": storage type associated with the instance disk template
64

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

    
74
  info = qa_utils.GetObjectInfo(["gnt-instance", "info", instance])[0]
75
  nodes = []
76
  for nodeinfo in info["Nodes"]:
77
    if "primary" in nodeinfo:
78
      nodes.append(nodeinfo["primary"])
79
    elif "secondaries" in nodeinfo:
80
      nodestr = nodeinfo["secondaries"]
81
      if nodestr:
82
        m = re_nodelist.match(nodestr)
83
        if m:
84
          nodes.extend(filter(None, m.groups()))
85
        else:
86
          nodes.append(nodestr)
87

    
88
  disk_template = info["Disk template"]
89
  if not disk_template:
90
    raise qa_error.Error("Can't get instance disk template")
91
  storage_type = constants.MAP_DISK_TEMPLATE_STORAGE_TYPE[disk_template]
92

    
93
  re_drbdnode = re.compile(r"^([^\s,]+),\s+minor=([0-9]+)$")
94
  vols = []
95
  drbd_min = {}
96
  for (count, diskinfo) in enumerate(info["Disks"]):
97
    (dtype, _) = diskinfo["disk/%s" % count].split(",", 1)
98
    if dtype == constants.DT_DRBD8:
99
      for child in diskinfo["child devices"]:
100
        vols.append(child["logical_id"])
101
      for key in ["nodeA", "nodeB"]:
102
        m = re_drbdnode.match(diskinfo[key])
103
        if not m:
104
          raise qa_error.Error("Cannot parse DRBD info: %s" % diskinfo[key])
105
        node = m.group(1)
106
        minor = int(m.group(2))
107
        minorlist = drbd_min.setdefault(node, [])
108
        minorlist.append(minor)
109
    elif dtype == constants.DT_PLAIN:
110
      vols.append(diskinfo["logical_id"])
111

    
112
  assert nodes
113
  assert len(nodes) < 2 or vols
114
  return {
115
    "nodes": nodes,
116
    "volumes": vols,
117
    "drbd-minors": drbd_min,
118
    "disk-template": disk_template,
119
    "storage-type": storage_type,
120
    }
121

    
122

    
123
def _DestroyInstanceDisks(instance):
124
  """Remove all the backend disks of an instance.
125

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

131
  """
132
  info = GetInstanceInfo(instance.name)
133
  # FIXME: destruction/removal should be part of the disk class
134
  if info["storage-type"] == constants.ST_LVM_VG:
135
    vols = info["volumes"]
136
    for node in info["nodes"]:
137
      AssertCommand(["lvremove", "-f"] + vols, node=node)
138
  elif info["storage-type"] == constants.ST_FILE:
139
    # Note that this works for both file and sharedfile, and this is intended.
140
    storage_dir = qa_config.get("file-storage-dir",
141
                                pathutils.DEFAULT_FILE_STORAGE_DIR)
142
    idir = os.path.join(storage_dir, instance.name)
143
    for node in info["nodes"]:
144
      AssertCommand(["rm", "-rf", idir], node=node)
145
  elif info["storage-type"] == constants.ST_DISKLESS:
146
    pass
147

    
148

    
149
def _GetInstanceField(instance, field):
150
  """Get the value of a field of an instance.
151

152
  @type instance: string
153
  @param instance: Instance name
154
  @type field: string
155
  @param field: Name of the field
156
  @rtype: string
157

158
  """
159
  master = qa_config.GetMasterNode()
160
  infocmd = utils.ShellQuoteArgs(["gnt-instance", "list", "--no-headers",
161
                                  "--units", "m", "-o", field, instance])
162
  return qa_utils.GetCommandOutput(master.primary, infocmd).strip()
163

    
164

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

168
  @type instance: string
169
  @param instance: Instance name
170
  @type field: string
171
  @param field: Name of the field
172
  @rtype: bool
173

174
  """
175
  info_out = _GetInstanceField(instance, field)
176
  if info_out == "Y":
177
    return True
178
  elif info_out == "N":
179
    return False
180
  else:
181
    raise qa_error.Error("Field %s of instance %s has a non-Boolean value:"
182
                         " %s" % (field, instance, info_out))
183

    
184

    
185
def _GetNumInstanceField(instance, field):
186
  """Get a numeric value of a field of an instance.
187

188
  @type instance: string
189
  @param instance: Instance name
190
  @type field: string
191
  @param field: Name of the field
192
  @rtype: int or float
193

194
  """
195
  info_out = _GetInstanceField(instance, field)
196
  try:
197
    ret = int(info_out)
198
  except ValueError:
199
    try:
200
      ret = float(info_out)
201
    except ValueError:
202
      raise qa_error.Error("Field %s of instance %s has a non-numeric value:"
203
                           " %s" % (field, instance, info_out))
204
  return ret
205

    
206

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

210
  @type instance: string
211
  @param instance: Instance name
212
  @type spec: string
213
  @param spec: one of the supported parameters: "memory-size", "cpu-count",
214
      "disk-count", "disk-size", "nic-count"
215
  @rtype: tuple
216
  @return: (minspec, maxspec); minspec and maxspec can be different only for
217
      memory and disk size
218

219
  """
220
  specmap = {
221
    "memory-size": ["be/minmem", "be/maxmem"],
222
    "cpu-count": ["vcpus"],
223
    "disk-count": ["disk.count"],
224
    "disk-size": ["disk.size/ "],
225
    "nic-count": ["nic.count"],
226
    }
227
  # For disks, first we need the number of disks
228
  if spec == "disk-size":
229
    (numdisk, _) = GetInstanceSpec(instance, "disk-count")
230
    fields = ["disk.size/%s" % k for k in range(0, numdisk)]
231
  else:
232
    assert spec in specmap, "%s not in %s" % (spec, specmap)
233
    fields = specmap[spec]
234
  values = [_GetNumInstanceField(instance, f) for f in fields]
235
  return (min(values), max(values))
236

    
237

    
238
def IsFailoverSupported(instance):
239
  return instance.disk_template in constants.DTS_MIRRORED
240

    
241

    
242
def IsMigrationSupported(instance):
243
  return instance.disk_template in constants.DTS_MIRRORED
244

    
245

    
246
def IsDiskReplacingSupported(instance):
247
  return instance.disk_template == constants.DT_DRBD8
248

    
249

    
250
def IsDiskSupported(instance):
251
  return instance.disk_template != constants.DT_DISKLESS
252

    
253

    
254
def TestInstanceAddWithPlainDisk(nodes, fail=False):
255
  """gnt-instance add -t plain"""
256
  if constants.DT_PLAIN in qa_config.GetEnabledDiskTemplates():
257
    instance = CreateInstanceByDiskTemplateOneNode(nodes, constants.DT_PLAIN,
258
                                                    fail=fail)
259
    if not fail:
260
      qa_utils.RunInstanceCheck(instance, True)
261
    return instance
262

    
263

    
264
@InstanceCheck(None, INST_UP, RETURN_VALUE)
265
def TestInstanceAddWithDrbdDisk(nodes):
266
  """gnt-instance add -t drbd"""
267
  if constants.DT_DRBD8 in qa_config.GetEnabledDiskTemplates():
268
    return CreateInstanceDrbd8(nodes)
269

    
270

    
271
@InstanceCheck(None, INST_UP, RETURN_VALUE)
272
def TestInstanceAddFile(nodes):
273
  """gnt-instance add -t file"""
274
  assert len(nodes) == 1
275
  if constants.DT_FILE in qa_config.GetEnabledDiskTemplates():
276
    return CreateInstanceByDiskTemplateOneNode(nodes, constants.DT_FILE)
277

    
278

    
279
@InstanceCheck(None, INST_UP, RETURN_VALUE)
280
def TestInstanceAddSharedFile(nodes):
281
  """gnt-instance add -t sharedfile"""
282
  assert len(nodes) == 1
283
  if constants.DT_SHARED_FILE in qa_config.GetEnabledDiskTemplates():
284
    return CreateInstanceByDiskTemplateOneNode(nodes, constants.DT_SHARED_FILE)
285

    
286

    
287
@InstanceCheck(None, INST_UP, RETURN_VALUE)
288
def TestInstanceAddDiskless(nodes):
289
  """gnt-instance add -t diskless"""
290
  assert len(nodes) == 1
291
  if constants.DT_DISKLESS in qa_config.GetEnabledDiskTemplates():
292
    return CreateInstanceByDiskTemplateOneNode(nodes, constants.DT_DISKLESS)
293

    
294

    
295
@InstanceCheck(None, INST_DOWN, FIRST_ARG)
296
def TestInstanceRemove(instance):
297
  """gnt-instance remove"""
298
  AssertCommand(["gnt-instance", "remove", "-f", instance.name])
299

    
300

    
301
@InstanceCheck(INST_DOWN, INST_UP, FIRST_ARG)
302
def TestInstanceStartup(instance):
303
  """gnt-instance startup"""
304
  AssertCommand(["gnt-instance", "startup", instance.name])
305

    
306

    
307
@InstanceCheck(INST_UP, INST_DOWN, FIRST_ARG)
308
def TestInstanceShutdown(instance):
309
  """gnt-instance shutdown"""
310
  AssertCommand(["gnt-instance", "shutdown", instance.name])
311

    
312

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

    
322
  AssertCommand(["gnt-instance", "shutdown", name])
323
  qa_utils.RunInstanceCheck(instance, False)
324
  AssertCommand(["gnt-instance", "reboot", name])
325

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

    
332

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

    
340
  AssertCommand(["gnt-instance", "reinstall", "-f", instance.name])
341

    
342
  # Test with non-existant OS definition
343
  AssertCommand(["gnt-instance", "reinstall", "-f",
344
                 "--os-type=NonExistantOsForQa",
345
                 instance.name],
346
                fail=True)
347

    
348

    
349
@InstanceCheck(INST_DOWN, INST_DOWN, FIRST_ARG)
350
def TestInstanceRenameAndBack(rename_source, rename_target):
351
  """gnt-instance rename
352

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

356
  """
357
  CheckSsconfInstanceList(rename_source)
358

    
359
  # first do a rename to a different actual name, expecting it to fail
360
  qa_utils.AddToEtcHosts(["meeeeh-not-exists", rename_target])
361
  try:
362
    AssertCommand(["gnt-instance", "rename", rename_source, rename_target],
363
                  fail=True)
364
    CheckSsconfInstanceList(rename_source)
365
  finally:
366
    qa_utils.RemoveFromEtcHosts(["meeeeh-not-exists", rename_target])
367

    
368
  info = GetInstanceInfo(rename_source)
369

    
370
  # Check instance volume tags correctly updated. Note that this check is lvm
371
  # specific, so we skip it for non-lvm-based instances.
372
  # FIXME: This will need updating when instances will be able to have
373
  # different disks living on storage pools with etherogeneous storage types.
374
  # FIXME: This check should be put inside the disk/storage class themselves,
375
  # rather than explicitly called here.
376
  if info["storage-type"] == constants.ST_LVM_VG:
377
    # In the lvm world we can check for tags on the logical volume
378
    tags_cmd = ("lvs -o tags --noheadings %s | grep " %
379
                (" ".join(info["volumes"]), ))
380
  else:
381
    # Other storage types don't have tags, so we use an always failing command,
382
    # to make sure it never gets executed
383
    tags_cmd = "false"
384

    
385
  # and now rename instance to rename_target...
386
  AssertCommand(["gnt-instance", "rename", rename_source, rename_target])
387
  CheckSsconfInstanceList(rename_target)
388
  qa_utils.RunInstanceCheck(rename_source, False)
389
  qa_utils.RunInstanceCheck(rename_target, False)
390

    
391
  # NOTE: tags might not be the exactly as the instance name, due to
392
  # charset restrictions; hence the test might be flaky
393
  if (rename_source != rename_target and
394
      info["storage-type"] == constants.ST_LVM_VG):
395
    for node in info["nodes"]:
396
      AssertCommand(tags_cmd + rename_source, node=node, fail=True)
397
      AssertCommand(tags_cmd + rename_target, node=node, fail=False)
398

    
399
  # and back
400
  AssertCommand(["gnt-instance", "rename", rename_target, rename_source])
401
  CheckSsconfInstanceList(rename_source)
402
  qa_utils.RunInstanceCheck(rename_target, False)
403

    
404
  if (rename_source != rename_target and
405
      info["storage-type"] == constants.ST_LVM_VG):
406
    for node in info["nodes"]:
407
      AssertCommand(tags_cmd + rename_source, node=node, fail=False)
408
      AssertCommand(tags_cmd + rename_target, node=node, fail=True)
409

    
410

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

    
419
  cmd = ["gnt-instance", "failover", "--force", instance.name]
420

    
421
  # failover ...
422
  AssertCommand(cmd)
423
  qa_utils.RunInstanceCheck(instance, True)
424

    
425
  # ... and back
426
  AssertCommand(cmd)
427

    
428

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

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

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

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

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

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

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

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

    
485
  AssertCommand(cmd)
486
  qa_utils.RunInstanceCheck(instance, True)
487

    
488

    
489
def TestInstanceInfo(instance):
490
  """gnt-instance info"""
491
  AssertCommand(["gnt-instance", "info", instance.name])
492

    
493

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

    
499
  # Assume /sbin/init exists on all systems
500
  test_kernel = "/sbin/init"
501
  test_initrd = test_kernel
502

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

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

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

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

    
526
  if default_hv == constants.HT_XEN_PVM:
527
    args.extend([
528
      ["-H", "%s=%s" % (constants.HV_INITRD_PATH, test_initrd)],
529
      ["-H", "no_%s" % (constants.HV_INITRD_PATH, )],
530
      ["-H", "%s=%s" % (constants.HV_INITRD_PATH, constants.VALUE_DEFAULT)],
531
      ])
532
  elif default_hv == constants.HT_XEN_HVM:
533
    args.extend([
534
      ["-H", "%s=acn" % constants.HV_BOOT_ORDER],
535
      ["-H", "%s=%s" % (constants.HV_BOOT_ORDER, constants.VALUE_DEFAULT)],
536
      ])
537
  elif default_hv == constants.HT_KVM and \
538
    qa_config.TestEnabled("instance-device-hotplug"):
539
    args.extend([
540
      ["--net", "-1:add", "--hotplug"],
541
      ["--net", "-1:modify,mac=aa:bb:cc:dd:ee:ff", "--hotplug"],
542
      ["--net", "-1:remove", "--hotplug"],
543
      ["--disk", "-1:add,size=1G", "--hotplug"],
544
      ["--disk", "-1:remove", "--hotplug"],
545
      ])
546

    
547
  for alist in args:
548
    AssertCommand(["gnt-instance", "modify"] + alist + [instance.name])
549

    
550
  # check no-modify
551
  AssertCommand(["gnt-instance", "modify", instance.name], fail=True)
552

    
553
  # Marking offline while instance is running must fail...
554
  AssertCommand(["gnt-instance", "modify", "--offline", instance.name],
555
                 fail=True)
556

    
557
  # ...while making it online is ok, and should work
558
  AssertCommand(["gnt-instance", "modify", "--online", instance.name])
559

    
560

    
561
@InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
562
def TestInstanceModifyPrimaryAndBack(instance, currentnode, othernode):
563
  """gnt-instance modify --new-primary
564

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

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

    
572
  cluster_name = qa_config.get("name")
573

    
574
  name = instance.name
575
  current = currentnode.primary
576
  other = othernode.primary
577

    
578
  filestorage = qa_config.get("file-storage-dir",
579
                              pathutils.DEFAULT_FILE_STORAGE_DIR)
580
  disk = os.path.join(filestorage, name)
581

    
582
  AssertCommand(["gnt-instance", "modify", "--new-primary=%s" % other, name],
583
                fail=True)
584
  AssertCommand(["gnt-instance", "shutdown", name])
585
  AssertCommand(["scp", "-oGlobalKnownHostsFile=%s" %
586
                 pathutils.SSH_KNOWN_HOSTS_FILE,
587
                 "-oCheckHostIp=no", "-oStrictHostKeyChecking=yes",
588
                 "-oHashKnownHosts=no", "-oHostKeyAlias=%s" % cluster_name,
589
                 "-r", disk, "%s:%s" % (other, filestorage)], node=current)
590
  AssertCommand(["gnt-instance", "modify", "--new-primary=%s" % other, name])
591
  AssertCommand(["gnt-instance", "startup", name])
592

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

    
599

    
600
@InstanceCheck(INST_DOWN, INST_DOWN, FIRST_ARG)
601
def TestInstanceStoppedModify(instance):
602
  """gnt-instance modify (stopped instance)"""
603
  name = instance.name
604

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

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

    
611
  # When the instance is offline shutdown should only work with --force,
612
  # while start should never work
613
  AssertCommand(["gnt-instance", "shutdown", name], fail=True)
614
  AssertCommand(["gnt-instance", "shutdown", "--force", name])
615
  AssertCommand(["gnt-instance", "start", name], fail=True)
616
  AssertCommand(["gnt-instance", "start", "--force", name], fail=True)
617

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

    
621
  # And online again
622
  AssertCommand(["gnt-instance", "modify", "--online", name])
623

    
624

    
625
@InstanceCheck(INST_DOWN, INST_DOWN, FIRST_ARG)
626
def TestInstanceConvertDiskToPlain(instance, inodes):
627
  """gnt-instance modify -t"""
628
  name = instance.name
629

    
630
  template = instance.disk_template
631
  if template != constants.DT_DRBD8:
632
    print qa_utils.FormatInfo("Unsupported template %s, skipping conversion"
633
                              " test" % template)
634
    return
635

    
636
  assert len(inodes) == 2
637
  AssertCommand(["gnt-instance", "modify", "-t", constants.DT_PLAIN, name])
638
  AssertCommand(["gnt-instance", "modify", "-t", constants.DT_DRBD8,
639
                 "-n", inodes[1].primary, name])
640

    
641

    
642
@InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
643
def TestInstanceModifyDisks(instance):
644
  """gnt-instance modify --disk"""
645
  if not IsDiskSupported(instance):
646
    print qa_utils.FormatInfo("Instance doesn't support disks, skipping test")
647
    return
648

    
649
  disk_conf = qa_config.GetDiskOptions()[-1]
650
  size = disk_conf.get("size")
651
  name = instance.name
652
  build_cmd = lambda arg: ["gnt-instance", "modify", "--disk", arg, name]
653
  if qa_config.AreSpindlesSupported():
654
    spindles = disk_conf.get("spindles")
655
    spindles_supported = True
656
  else:
657
    # Any number is good for spindles in this case
658
    spindles = 1
659
    spindles_supported = False
660
  AssertCommand(build_cmd("add:size=%s,spindles=%s" % (size, spindles)),
661
                fail=not spindles_supported)
662
  AssertCommand(build_cmd("add:size=%s" % size),
663
                fail=spindles_supported)
664
  # Exactly one of the above commands has succeded, so we need one remove
665
  AssertCommand(build_cmd("remove"))
666

    
667

    
668
@InstanceCheck(INST_DOWN, INST_DOWN, FIRST_ARG)
669
def TestInstanceGrowDisk(instance):
670
  """gnt-instance grow-disk"""
671
  if instance.disk_template == constants.DT_DISKLESS:
672
    print qa_utils.FormatInfo("Test not supported for diskless instances")
673
    return
674

    
675
  name = instance.name
676
  disks = qa_config.GetDiskOptions()
677
  all_size = [d.get("size") for d in disks]
678
  all_grow = [d.get("growth") for d in disks]
679

    
680
  if not all_grow:
681
    # missing disk sizes but instance grow disk has been enabled,
682
    # let's set fixed/nomimal growth
683
    all_grow = ["128M" for _ in all_size]
684

    
685
  for idx, (size, grow) in enumerate(zip(all_size, all_grow)):
686
    # succeed in grow by amount
687
    AssertCommand(["gnt-instance", "grow-disk", name, str(idx), grow])
688
    # fail in grow to the old size
689
    AssertCommand(["gnt-instance", "grow-disk", "--absolute", name, str(idx),
690
                   size], fail=True)
691
    # succeed to grow to old size + 2 * growth
692
    int_size = utils.ParseUnit(size)
693
    int_grow = utils.ParseUnit(grow)
694
    AssertCommand(["gnt-instance", "grow-disk", "--absolute", name, str(idx),
695
                   str(int_size + 2 * int_grow)])
696

    
697

    
698
@InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
699
def TestInstanceDeviceNames(instance):
700
  if instance.disk_template == constants.DT_DISKLESS:
701
    print qa_utils.FormatInfo("Test not supported for diskless instances")
702
    return
703

    
704
  name = instance.name
705
  for dev_type in ["disk", "net"]:
706
    if dev_type == "disk":
707
      options = ",size=512M"
708
      if qa_config.AreSpindlesSupported():
709
        options += ",spindles=1"
710
    else:
711
      options = ""
712
    # succeed in adding a device named 'test_device'
713
    AssertCommand(["gnt-instance", "modify",
714
                   "--%s=-1:add,name=test_device%s" % (dev_type, options),
715
                   name])
716
    # succeed in removing the 'test_device'
717
    AssertCommand(["gnt-instance", "modify",
718
                   "--%s=test_device:remove" % dev_type,
719
                   name])
720
    # fail to add two devices with the same name
721
    AssertCommand(["gnt-instance", "modify",
722
                   "--%s=-1:add,name=test_device%s" % (dev_type, options),
723
                   "--%s=-1:add,name=test_device%s" % (dev_type, options),
724
                   name], fail=True)
725
    # fail to add a device with invalid name
726
    AssertCommand(["gnt-instance", "modify",
727
                   "--%s=-1:add,name=2%s" % (dev_type, options),
728
                   name], fail=True)
729
  # Rename disks
730
  disks = qa_config.GetDiskOptions()
731
  disk_names = [d.get("name") for d in disks]
732
  for idx, disk_name in enumerate(disk_names):
733
    # Refer to disk by idx
734
    AssertCommand(["gnt-instance", "modify",
735
                   "--disk=%s:modify,name=renamed" % idx,
736
                   name])
737
    # Refer to by name and rename to original name
738
    AssertCommand(["gnt-instance", "modify",
739
                   "--disk=renamed:modify,name=%s" % disk_name,
740
                   name])
741
  if len(disks) >= 2:
742
    # fail in renaming to disks to the same name
743
    AssertCommand(["gnt-instance", "modify",
744
                   "--disk=0:modify,name=same_name",
745
                   "--disk=1:modify,name=same_name",
746
                   name], fail=True)
747

    
748

    
749
def TestInstanceList():
750
  """gnt-instance list"""
751
  qa_utils.GenericQueryTest("gnt-instance", query.INSTANCE_FIELDS.keys())
752

    
753

    
754
def TestInstanceListFields():
755
  """gnt-instance list-fields"""
756
  qa_utils.GenericQueryFieldsTest("gnt-instance", query.INSTANCE_FIELDS.keys())
757

    
758

    
759
@InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
760
def TestInstanceConsole(instance):
761
  """gnt-instance console"""
762
  AssertCommand(["gnt-instance", "console", "--show-cmd", instance.name])
763

    
764

    
765
@InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
766
def TestReplaceDisks(instance, curr_nodes, other_nodes):
767
  """gnt-instance replace-disks"""
768
  def buildcmd(args):
769
    cmd = ["gnt-instance", "replace-disks"]
770
    cmd.extend(args)
771
    cmd.append(instance.name)
772
    return cmd
773

    
774
  if not IsDiskReplacingSupported(instance):
775
    print qa_utils.FormatInfo("Instance doesn't support disk replacing,"
776
                              " skipping test")
777
    return
778

    
779
  # Currently all supported templates have one primary and one secondary node
780
  assert len(curr_nodes) == 2
781
  snode = curr_nodes[1]
782
  assert len(other_nodes) == 1
783
  othernode = other_nodes[0]
784

    
785
  options = qa_config.get("options", {})
786
  use_ialloc = options.get("use-iallocators", True)
787
  for data in [
788
    ["-p"],
789
    ["-s"],
790
    # A placeholder; the actual command choice depends on use_ialloc
791
    None,
792
    # Restore the original secondary
793
    ["--new-secondary=%s" % snode.primary],
794
    ]:
795
    if data is None:
796
      if use_ialloc:
797
        data = ["-I", constants.DEFAULT_IALLOCATOR_SHORTCUT]
798
      else:
799
        data = ["--new-secondary=%s" % othernode.primary]
800
    AssertCommand(buildcmd(data))
801

    
802
  AssertCommand(buildcmd(["-a"]))
803
  AssertCommand(["gnt-instance", "stop", instance.name])
804
  AssertCommand(buildcmd(["-a"]), fail=True)
805
  AssertCommand(["gnt-instance", "activate-disks", instance.name])
806
  AssertCommand(["gnt-instance", "activate-disks", "--wait-for-sync",
807
                 instance.name])
808
  AssertCommand(buildcmd(["-a"]))
809
  AssertCommand(["gnt-instance", "start", instance.name])
810

    
811

    
812
def _AssertRecreateDisks(cmdargs, instance, fail=False, check=True,
813
                         destroy=True):
814
  """Execute gnt-instance recreate-disks and check the result
815

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

822
  """
823
  if destroy:
824
    _DestroyInstanceDisks(instance)
825
  AssertCommand((["gnt-instance", "recreate-disks"] + cmdargs +
826
                 [instance.name]), fail)
827
  if not fail and check:
828
    # Quick check that the disks are there
829
    AssertCommand(["gnt-instance", "activate-disks", instance.name])
830
    AssertCommand(["gnt-instance", "activate-disks", "--wait-for-sync",
831
                   instance.name])
832
    AssertCommand(["gnt-instance", "deactivate-disks", instance.name])
833

    
834

    
835
def _BuildRecreateDisksOpts(en_disks, with_spindles, with_growth,
836
                            spindles_supported):
837
  if with_spindles:
838
    if spindles_supported:
839
      if with_growth:
840
        build_spindles_opt = (lambda disk:
841
                              ",spindles=%s" %
842
                              (disk["spindles"] + disk["spindles-growth"]))
843
      else:
844
        build_spindles_opt = (lambda disk:
845
                              ",spindles=%s" % disk["spindles"])
846
    else:
847
      build_spindles_opt = (lambda _: ",spindles=1")
848
  else:
849
    build_spindles_opt = (lambda _: "")
850
  if with_growth:
851
    build_size_opt = (lambda disk:
852
                      "size=%s" % (utils.ParseUnit(disk["size"]) +
853
                                   utils.ParseUnit(disk["growth"])))
854
  else:
855
    build_size_opt = (lambda disk: "size=%s" % disk["size"])
856
  build_disk_opt = (lambda (idx, disk):
857
                    "--disk=%s:%s%s" % (idx, build_size_opt(disk),
858
                                        build_spindles_opt(disk)))
859
  return map(build_disk_opt, en_disks)
860

    
861

    
862
@InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
863
def TestRecreateDisks(instance, inodes, othernodes):
864
  """gnt-instance recreate-disks
865

866
  @param instance: Instance to work on
867
  @param inodes: List of the current nodes of the instance
868
  @param othernodes: list/tuple of nodes where to temporarily recreate disks
869

870
  """
871
  options = qa_config.get("options", {})
872
  use_ialloc = options.get("use-iallocators", True)
873
  other_seq = ":".join([n.primary for n in othernodes])
874
  orig_seq = ":".join([n.primary for n in inodes])
875
  # These fail because the instance is running
876
  _AssertRecreateDisks(["-n", other_seq], instance, fail=True, destroy=False)
877
  if use_ialloc:
878
    _AssertRecreateDisks(["-I", "hail"], instance, fail=True, destroy=False)
879
  else:
880
    _AssertRecreateDisks(["-n", other_seq], instance, fail=True, destroy=False)
881
  AssertCommand(["gnt-instance", "stop", instance.name])
882
  # Disks exist: this should fail
883
  _AssertRecreateDisks([], instance, fail=True, destroy=False)
884
  # Unsupported spindles parameters: fail
885
  if not qa_config.AreSpindlesSupported():
886
    _AssertRecreateDisks(["--disk=0:spindles=2"], instance,
887
                         fail=True, destroy=False)
888
  # Recreate disks in place
889
  _AssertRecreateDisks([], instance)
890
  # Move disks away
891
  if use_ialloc:
892
    _AssertRecreateDisks(["-I", "hail"], instance)
893
    # Move disks somewhere else
894
    _AssertRecreateDisks(["-I", constants.DEFAULT_IALLOCATOR_SHORTCUT],
895
                         instance)
896
  else:
897
    _AssertRecreateDisks(["-n", other_seq], instance)
898
  # Move disks back
899
  _AssertRecreateDisks(["-n", orig_seq], instance)
900
  # Recreate resized disks
901
  # One of the two commands fails because either spindles are given when they
902
  # should not or vice versa
903
  alldisks = qa_config.GetDiskOptions()
904
  spindles_supported = qa_config.AreSpindlesSupported()
905
  disk_opts = _BuildRecreateDisksOpts(enumerate(alldisks), True, True,
906
                                      spindles_supported)
907
  _AssertRecreateDisks(disk_opts, instance, destroy=True,
908
                       fail=not spindles_supported)
909
  disk_opts = _BuildRecreateDisksOpts(enumerate(alldisks), False, True,
910
                                      spindles_supported)
911
  _AssertRecreateDisks(disk_opts, instance, destroy=False,
912
                       fail=spindles_supported)
913
  # Recreate the disks one by one (with the original size)
914
  for (idx, disk) in enumerate(alldisks):
915
    # Only the first call should destroy all the disk
916
    destroy = (idx == 0)
917
    # Again, one of the two commands is expected to fail
918
    disk_opts = _BuildRecreateDisksOpts([(idx, disk)], True, False,
919
                                        spindles_supported)
920
    _AssertRecreateDisks(disk_opts, instance, destroy=destroy, check=False,
921
                         fail=not spindles_supported)
922
    disk_opts = _BuildRecreateDisksOpts([(idx, disk)], False, False,
923
                                        spindles_supported)
924
    _AssertRecreateDisks(disk_opts, instance, destroy=False, check=False,
925
                         fail=spindles_supported)
926
  # This and InstanceCheck decoration check that the disks are working
927
  AssertCommand(["gnt-instance", "reinstall", "-f", instance.name])
928
  AssertCommand(["gnt-instance", "start", instance.name])
929

    
930

    
931
@InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
932
def TestInstanceExport(instance, node):
933
  """gnt-backup export -n ..."""
934
  name = instance.name
935
  # Export does not work for file-based templates, thus we skip the test
936
  if instance.disk_template in [constants.DT_FILE, constants.DT_SHARED_FILE]:
937
    return
938
  AssertCommand(["gnt-backup", "export", "-n", node.primary, name])
939
  return qa_utils.ResolveInstanceName(name)
940

    
941

    
942
@InstanceCheck(None, INST_DOWN, FIRST_ARG)
943
def TestInstanceExportWithRemove(instance, node):
944
  """gnt-backup export --remove-instance"""
945
  AssertCommand(["gnt-backup", "export", "-n", node.primary,
946
                 "--remove-instance", instance.name])
947

    
948

    
949
@InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
950
def TestInstanceExportNoTarget(instance):
951
  """gnt-backup export (without target node, should fail)"""
952
  AssertCommand(["gnt-backup", "export", instance.name], fail=True)
953

    
954

    
955
@InstanceCheck(None, INST_DOWN, FIRST_ARG)
956
def TestInstanceImport(newinst, node, expnode, name):
957
  """gnt-backup import"""
958
  templ = constants.DT_PLAIN
959
  if not qa_config.IsTemplateSupported(templ):
960
    return
961
  cmd = (["gnt-backup", "import",
962
          "--disk-template=%s" % templ,
963
          "--no-ip-check",
964
          "--src-node=%s" % expnode.primary,
965
          "--src-dir=%s/%s" % (pathutils.EXPORT_DIR, name),
966
          "--node=%s" % node.primary] +
967
         GetGenericAddParameters(newinst, templ,
968
                                  force_mac=constants.VALUE_GENERATE))
969
  cmd.append(newinst.name)
970
  AssertCommand(cmd)
971
  newinst.SetDiskTemplate(templ)
972

    
973

    
974
def TestBackupList(expnode):
975
  """gnt-backup list"""
976
  AssertCommand(["gnt-backup", "list", "--node=%s" % expnode.primary])
977

    
978
  qa_utils.GenericQueryTest("gnt-backup", query.EXPORT_FIELDS.keys(),
979
                            namefield=None, test_unknown=False)
980

    
981

    
982
def TestBackupListFields():
983
  """gnt-backup list-fields"""
984
  qa_utils.GenericQueryFieldsTest("gnt-backup", query.EXPORT_FIELDS.keys())
985

    
986

    
987
def TestRemoveInstanceOfflineNode(instance, snode, set_offline, set_online):
988
  """gnt-instance remove with an off-line node
989

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

995
  """
996
  info = GetInstanceInfo(instance.name)
997
  set_offline(snode)
998
  try:
999
    TestInstanceRemove(instance)
1000
  finally:
1001
    set_online(snode)
1002

    
1003
  # Clean up the disks on the offline node, if necessary
1004
  if instance.disk_template not in constants.DTS_EXT_MIRROR:
1005
    # FIXME: abstract the cleanup inside the disks
1006
    if info["storage-type"] == constants.ST_LVM_VG:
1007
      for minor in info["drbd-minors"][snode.primary]:
1008
        # DRBD 8.3 syntax comes first, then DRBD 8.4 syntax. The 8.4 syntax
1009
        # relies on the fact that we always create a resources for each minor,
1010
        # and that this resources is always named resource{minor}.
1011
        # As 'drbdsetup 0 down' does return success (even though that's invalid
1012
        # syntax), we always have to perform both commands and ignore the
1013
        # output.
1014
        drbd_shutdown_cmd = \
1015
          "(drbdsetup %d down >/dev/null 2>&1;" \
1016
          " drbdsetup down resource%d >/dev/null 2>&1) || /bin/true" % \
1017
            (minor, minor)
1018
        AssertCommand(drbd_shutdown_cmd, node=snode)
1019
      AssertCommand(["lvremove", "-f"] + info["volumes"], node=snode)
1020
    elif info["storage-type"] == constants.ST_FILE:
1021
      filestorage = qa_config.get("file-storage-dir",
1022
                                  pathutils.DEFAULT_FILE_STORAGE_DIR)
1023
      disk = os.path.join(filestorage, instance.name)
1024
      AssertCommand(["rm", "-rf", disk], node=snode)
1025

    
1026

    
1027
def TestInstanceCreationRestrictedByDiskTemplates():
1028
  """Test adding instances for disabled disk templates."""
1029
  if qa_config.TestEnabled("cluster-exclusive-storage"):
1030
    # These tests are valid only for non-exclusive storage
1031
    return
1032

    
1033
  enabled_disk_templates = qa_config.GetEnabledDiskTemplates()
1034
  nodes = qa_config.AcquireManyNodes(2)
1035

    
1036
  # Setup the cluster with the enabled_disk_templates
1037
  AssertCommand(
1038
    ["gnt-cluster", "modify",
1039
     "--enabled-disk-templates=%s" % ",".join(enabled_disk_templates),
1040
     "--ipolicy-disk-templates=%s" % ",".join(enabled_disk_templates)],
1041
    fail=False)
1042

    
1043
  # Test instance creation for enabled disk templates
1044
  for disk_template in enabled_disk_templates:
1045
    instance = CreateInstanceByDiskTemplate(nodes, disk_template, fail=False)
1046
    TestInstanceRemove(instance)
1047
    instance.Release()
1048

    
1049
  # Test that instance creation fails for disabled disk templates
1050
  disabled_disk_templates = list(constants.DISK_TEMPLATES
1051
                                 - set(enabled_disk_templates))
1052
  for disk_template in disabled_disk_templates:
1053
    instance = CreateInstanceByDiskTemplate(nodes, disk_template, fail=True)
1054

    
1055
  # Test instance creation for after disabling enabled disk templates
1056
  if (len(enabled_disk_templates) > 1):
1057
    # Partition the disk templates, enable them separately and check if the
1058
    # disabled ones cannot be used by instances.
1059
    middle = len(enabled_disk_templates) / 2
1060
    templates1 = enabled_disk_templates[:middle]
1061
    templates2 = enabled_disk_templates[middle:]
1062

    
1063
    for (enabled, disabled) in [(templates1, templates2),
1064
                                (templates2, templates1)]:
1065
      AssertCommand(["gnt-cluster", "modify",
1066
                     "--enabled-disk-templates=%s" % ",".join(enabled),
1067
                     "--ipolicy-disk-templates=%s" % ",".join(enabled)],
1068
                    fail=False)
1069
      for disk_template in disabled:
1070
        CreateInstanceByDiskTemplate(nodes, disk_template, fail=True)
1071
  elif (len(enabled_disk_templates) == 1):
1072
    # If only one disk template is enabled in the QA config, we have to enable
1073
    # some other templates in order to test if the disabling the only enabled
1074
    # disk template prohibits creating instances of that template.
1075
    other_disk_templates = list(
1076
                             set([constants.DT_DISKLESS, constants.DT_BLOCK]) -
1077
                             set(enabled_disk_templates))
1078
    AssertCommand(["gnt-cluster", "modify",
1079
                   "--enabled-disk-templates=%s" %
1080
                     ",".join(other_disk_templates),
1081
                   "--ipolicy-disk-templates=%s" %
1082
                     ",".join(other_disk_templates)],
1083
                  fail=False)
1084
    CreateInstanceByDiskTemplate(nodes, enabled_disk_templates[0], fail=True)
1085
  else:
1086
    raise qa_error.Error("Please enable at least one disk template"
1087
                         " in your QA setup.")
1088

    
1089
  # Restore initially enabled disk templates
1090
  AssertCommand(["gnt-cluster", "modify",
1091
                 "--enabled-disk-templates=%s" %
1092
                   ",".join(enabled_disk_templates),
1093
                 "--ipolicy-disk-templates=%s" %
1094
                   ",".join(enabled_disk_templates)],
1095
                 fail=False)