Statistics
| Branch: | Tag: | Revision:

root / qa / qa_instance.py @ b780c231

History | View | Annotate | Download (38.3 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.DISK_TEMPLATES_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.LD_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.LD_LV:
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
    # FIXME: file storage dir not configurable in qa
140
    # Note that this works for both file and sharedfile, and this is intended.
141
    filestorage = pathutils.DEFAULT_FILE_STORAGE_DIR
142
    idir = os.path.join(filestorage, 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 TestInstanceAddDiskless(nodes):
281
  """gnt-instance add -t diskless"""
282
  assert len(nodes) == 1
283
  if constants.DT_DISKLESS in qa_config.GetEnabledDiskTemplates():
284
    return CreateInstanceByDiskTemplateOneNode(nodes, constants.DT_DISKLESS)
285

    
286

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

    
292

    
293
@InstanceCheck(INST_DOWN, INST_UP, FIRST_ARG)
294
def TestInstanceStartup(instance):
295
  """gnt-instance startup"""
296
  AssertCommand(["gnt-instance", "startup", instance.name])
297

    
298

    
299
@InstanceCheck(INST_UP, INST_DOWN, FIRST_ARG)
300
def TestInstanceShutdown(instance):
301
  """gnt-instance shutdown"""
302
  AssertCommand(["gnt-instance", "shutdown", instance.name])
303

    
304

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

    
314
  AssertCommand(["gnt-instance", "shutdown", name])
315
  qa_utils.RunInstanceCheck(instance, False)
316
  AssertCommand(["gnt-instance", "reboot", name])
317

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

    
324

    
325
@InstanceCheck(INST_DOWN, INST_DOWN, FIRST_ARG)
326
def TestInstanceReinstall(instance):
327
  """gnt-instance reinstall"""
328
  if instance.disk_template == constants.DT_DISKLESS:
329
    print qa_utils.FormatInfo("Test not supported for diskless instances")
330
    return
331

    
332
  AssertCommand(["gnt-instance", "reinstall", "-f", instance.name])
333

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

    
340

    
341
@InstanceCheck(INST_DOWN, INST_DOWN, FIRST_ARG)
342
def TestInstanceRenameAndBack(rename_source, rename_target):
343
  """gnt-instance rename
344

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

348
  """
349
  CheckSsconfInstanceList(rename_source)
350

    
351
  # first do a rename to a different actual name, expecting it to fail
352
  qa_utils.AddToEtcHosts(["meeeeh-not-exists", rename_target])
353
  try:
354
    AssertCommand(["gnt-instance", "rename", rename_source, rename_target],
355
                  fail=True)
356
    CheckSsconfInstanceList(rename_source)
357
  finally:
358
    qa_utils.RemoveFromEtcHosts(["meeeeh-not-exists", rename_target])
359

    
360
  info = GetInstanceInfo(rename_source)
361

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

    
377
  # and now rename instance to rename_target...
378
  AssertCommand(["gnt-instance", "rename", rename_source, rename_target])
379
  CheckSsconfInstanceList(rename_target)
380
  qa_utils.RunInstanceCheck(rename_source, False)
381
  qa_utils.RunInstanceCheck(rename_target, False)
382

    
383
  # NOTE: tags might not be the exactly as the instance name, due to
384
  # charset restrictions; hence the test might be flaky
385
  if (rename_source != rename_target and
386
      info["storage-type"] == constants.ST_LVM_VG):
387
    for node in info["nodes"]:
388
      AssertCommand(tags_cmd + rename_source, node=node, fail=True)
389
      AssertCommand(tags_cmd + rename_target, node=node, fail=False)
390

    
391
  # and back
392
  AssertCommand(["gnt-instance", "rename", rename_target, rename_source])
393
  CheckSsconfInstanceList(rename_source)
394
  qa_utils.RunInstanceCheck(rename_target, False)
395

    
396
  if (rename_source != rename_target and
397
      info["storage-type"] == constants.ST_LVM_VG):
398
    for node in info["nodes"]:
399
      AssertCommand(tags_cmd + rename_source, node=node, fail=False)
400
      AssertCommand(tags_cmd + rename_target, node=node, fail=True)
401

    
402

    
403
@InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
404
def TestInstanceFailover(instance):
405
  """gnt-instance failover"""
406
  if not IsFailoverSupported(instance):
407
    print qa_utils.FormatInfo("Instance doesn't support failover, skipping"
408
                              " test")
409
    return
410

    
411
  cmd = ["gnt-instance", "failover", "--force", instance.name]
412

    
413
  # failover ...
414
  AssertCommand(cmd)
415
  qa_utils.RunInstanceCheck(instance, True)
416

    
417
  # ... and back
418
  AssertCommand(cmd)
419

    
420

    
421
@InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
422
def TestInstanceMigrate(instance, toggle_always_failover=True):
423
  """gnt-instance migrate"""
424
  if not IsMigrationSupported(instance):
425
    print qa_utils.FormatInfo("Instance doesn't support migration, skipping"
426
                              " test")
427
    return
428

    
429
  cmd = ["gnt-instance", "migrate", "--force", instance.name]
430
  af_par = constants.BE_ALWAYS_FAILOVER
431
  af_field = "be/" + constants.BE_ALWAYS_FAILOVER
432
  af_init_val = _GetBoolInstanceField(instance.name, af_field)
433

    
434
  # migrate ...
435
  AssertCommand(cmd)
436
  # TODO: Verify the choice between failover and migration
437
  qa_utils.RunInstanceCheck(instance, True)
438

    
439
  # ... and back (possibly with always_failover toggled)
440
  if toggle_always_failover:
441
    AssertCommand(["gnt-instance", "modify", "-B",
442
                   ("%s=%s" % (af_par, not af_init_val)),
443
                   instance.name])
444
  AssertCommand(cmd)
445
  # TODO: Verify the choice between failover and migration
446
  qa_utils.RunInstanceCheck(instance, True)
447
  if toggle_always_failover:
448
    AssertCommand(["gnt-instance", "modify", "-B",
449
                   ("%s=%s" % (af_par, af_init_val)), instance.name])
450

    
451
  # TODO: Split into multiple tests
452
  AssertCommand(["gnt-instance", "shutdown", instance.name])
453
  qa_utils.RunInstanceCheck(instance, False)
454
  AssertCommand(cmd, fail=True)
455
  AssertCommand(["gnt-instance", "migrate", "--force", "--allow-failover",
456
                 instance.name])
457
  AssertCommand(["gnt-instance", "start", instance.name])
458
  AssertCommand(cmd)
459
  # @InstanceCheck enforces the check that the instance is running
460
  qa_utils.RunInstanceCheck(instance, True)
461

    
462
  AssertCommand(["gnt-instance", "modify", "-B",
463
                 ("%s=%s" %
464
                  (constants.BE_ALWAYS_FAILOVER, constants.VALUE_TRUE)),
465
                 instance.name])
466

    
467
  AssertCommand(cmd)
468
  qa_utils.RunInstanceCheck(instance, True)
469
  # TODO: Verify that a failover has been done instead of a migration
470

    
471
  # TODO: Verify whether the default value is restored here (not hardcoded)
472
  AssertCommand(["gnt-instance", "modify", "-B",
473
                 ("%s=%s" %
474
                  (constants.BE_ALWAYS_FAILOVER, constants.VALUE_FALSE)),
475
                 instance.name])
476

    
477
  AssertCommand(cmd)
478
  qa_utils.RunInstanceCheck(instance, True)
479

    
480

    
481
def TestInstanceInfo(instance):
482
  """gnt-instance info"""
483
  AssertCommand(["gnt-instance", "info", instance.name])
484

    
485

    
486
@InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
487
def TestInstanceModify(instance):
488
  """gnt-instance modify"""
489
  default_hv = qa_config.GetDefaultHypervisor()
490

    
491
  # Assume /sbin/init exists on all systems
492
  test_kernel = "/sbin/init"
493
  test_initrd = test_kernel
494

    
495
  orig_maxmem = qa_config.get(constants.BE_MAXMEM)
496
  orig_minmem = qa_config.get(constants.BE_MINMEM)
497
  #orig_bridge = qa_config.get("bridge", "xen-br0")
498

    
499
  args = [
500
    ["-B", "%s=128" % constants.BE_MINMEM],
501
    ["-B", "%s=128" % constants.BE_MAXMEM],
502
    ["-B", "%s=%s,%s=%s" % (constants.BE_MINMEM, orig_minmem,
503
                            constants.BE_MAXMEM, orig_maxmem)],
504
    ["-B", "%s=2" % constants.BE_VCPUS],
505
    ["-B", "%s=1" % constants.BE_VCPUS],
506
    ["-B", "%s=%s" % (constants.BE_VCPUS, constants.VALUE_DEFAULT)],
507
    ["-B", "%s=%s" % (constants.BE_ALWAYS_FAILOVER, constants.VALUE_TRUE)],
508
    ["-B", "%s=%s" % (constants.BE_ALWAYS_FAILOVER, constants.VALUE_DEFAULT)],
509

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

    
513
    # TODO: bridge tests
514
    #["--bridge", "xen-br1"],
515
    #["--bridge", orig_bridge],
516
    ]
517

    
518
  if default_hv == constants.HT_XEN_PVM:
519
    args.extend([
520
      ["-H", "%s=%s" % (constants.HV_INITRD_PATH, test_initrd)],
521
      ["-H", "no_%s" % (constants.HV_INITRD_PATH, )],
522
      ["-H", "%s=%s" % (constants.HV_INITRD_PATH, constants.VALUE_DEFAULT)],
523
      ])
524
  elif default_hv == constants.HT_XEN_HVM:
525
    args.extend([
526
      ["-H", "%s=acn" % constants.HV_BOOT_ORDER],
527
      ["-H", "%s=%s" % (constants.HV_BOOT_ORDER, constants.VALUE_DEFAULT)],
528
      ])
529

    
530
  for alist in args:
531
    AssertCommand(["gnt-instance", "modify"] + alist + [instance.name])
532

    
533
  # check no-modify
534
  AssertCommand(["gnt-instance", "modify", instance.name], fail=True)
535

    
536
  # Marking offline while instance is running must fail...
537
  AssertCommand(["gnt-instance", "modify", "--offline", instance.name],
538
                 fail=True)
539

    
540
  # ...while making it online is ok, and should work
541
  AssertCommand(["gnt-instance", "modify", "--online", instance.name])
542

    
543

    
544
@InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
545
def TestInstanceModifyPrimaryAndBack(instance, currentnode, othernode):
546
  """gnt-instance modify --new-primary
547

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

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

    
555
  cluster_name = qa_config.get("name")
556

    
557
  name = instance.name
558
  current = currentnode.primary
559
  other = othernode.primary
560

    
561
  # FIXME: the qa doesn't have a customizable file storage dir parameter. As
562
  # such for now we use the default.
563
  filestorage = pathutils.DEFAULT_FILE_STORAGE_DIR
564
  disk = os.path.join(filestorage, name)
565

    
566
  AssertCommand(["gnt-instance", "modify", "--new-primary=%s" % other, name],
567
                fail=True)
568
  AssertCommand(["gnt-instance", "shutdown", name])
569
  AssertCommand(["scp", "-oGlobalKnownHostsFile=%s" %
570
                 pathutils.SSH_KNOWN_HOSTS_FILE,
571
                 "-oCheckHostIp=no", "-oStrictHostKeyChecking=yes",
572
                 "-oHashKnownHosts=no", "-oHostKeyAlias=%s" % cluster_name,
573
                 "-r", disk, "%s:%s" % (other, filestorage)], node=current)
574
  AssertCommand(["gnt-instance", "modify", "--new-primary=%s" % other, name])
575
  AssertCommand(["gnt-instance", "startup", name])
576

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

    
583

    
584
@InstanceCheck(INST_DOWN, INST_DOWN, FIRST_ARG)
585
def TestInstanceStoppedModify(instance):
586
  """gnt-instance modify (stopped instance)"""
587
  name = instance.name
588

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

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

    
595
  # When the instance is offline shutdown should only work with --force,
596
  # while start should never work
597
  AssertCommand(["gnt-instance", "shutdown", name], fail=True)
598
  AssertCommand(["gnt-instance", "shutdown", "--force", name])
599
  AssertCommand(["gnt-instance", "start", name], fail=True)
600
  AssertCommand(["gnt-instance", "start", "--force", name], fail=True)
601

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

    
605
  # And online again
606
  AssertCommand(["gnt-instance", "modify", "--online", name])
607

    
608

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

    
614
  template = instance.disk_template
615
  if template != constants.DT_DRBD8:
616
    print qa_utils.FormatInfo("Unsupported template %s, skipping conversion"
617
                              " test" % template)
618
    return
619

    
620
  assert len(inodes) == 2
621
  AssertCommand(["gnt-instance", "modify", "-t", constants.DT_PLAIN, name])
622
  AssertCommand(["gnt-instance", "modify", "-t", constants.DT_DRBD8,
623
                 "-n", inodes[1].primary, name])
624

    
625

    
626
@InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
627
def TestInstanceModifyDisks(instance):
628
  """gnt-instance modify --disk"""
629
  if not IsDiskSupported(instance):
630
    print qa_utils.FormatInfo("Instance doesn't support disks, skipping test")
631
    return
632

    
633
  disk_conf = qa_config.GetDiskOptions()[-1]
634
  size = disk_conf.get("size")
635
  name = instance.name
636
  build_cmd = lambda arg: ["gnt-instance", "modify", "--disk", arg, name]
637
  if qa_config.AreSpindlesSupported():
638
    spindles = disk_conf.get("spindles")
639
    spindles_supported = True
640
  else:
641
    # Any number is good for spindles in this case
642
    spindles = 1
643
    spindles_supported = False
644
  AssertCommand(build_cmd("add:size=%s,spindles=%s" % (size, spindles)),
645
                fail=not spindles_supported)
646
  AssertCommand(build_cmd("add:size=%s" % size),
647
                fail=spindles_supported)
648
  # Exactly one of the above commands has succeded, so we need one remove
649
  AssertCommand(build_cmd("remove"))
650

    
651

    
652
@InstanceCheck(INST_DOWN, INST_DOWN, FIRST_ARG)
653
def TestInstanceGrowDisk(instance):
654
  """gnt-instance grow-disk"""
655
  if instance.disk_template == constants.DT_DISKLESS:
656
    print qa_utils.FormatInfo("Test not supported for diskless instances")
657
    return
658

    
659
  name = instance.name
660
  disks = qa_config.GetDiskOptions()
661
  all_size = [d.get("size") for d in disks]
662
  all_grow = [d.get("growth") for d in disks]
663

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

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

    
681

    
682
@InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
683
def TestInstanceDeviceNames(instance):
684
  if instance.disk_template == constants.DT_DISKLESS:
685
    print qa_utils.FormatInfo("Test not supported for diskless instances")
686
    return
687

    
688
  name = instance.name
689
  for dev_type in ["disk", "net"]:
690
    if dev_type == "disk":
691
      options = ",size=512M"
692
      if qa_config.AreSpindlesSupported():
693
        options += ",spindles=1"
694
    else:
695
      options = ""
696
    # succeed in adding a device named 'test_device'
697
    AssertCommand(["gnt-instance", "modify",
698
                   "--%s=-1:add,name=test_device%s" % (dev_type, options),
699
                   name])
700
    # succeed in removing the 'test_device'
701
    AssertCommand(["gnt-instance", "modify",
702
                   "--%s=test_device:remove" % dev_type,
703
                   name])
704
    # fail to add two devices with the same name
705
    AssertCommand(["gnt-instance", "modify",
706
                   "--%s=-1:add,name=test_device%s" % (dev_type, options),
707
                   "--%s=-1:add,name=test_device%s" % (dev_type, options),
708
                   name], fail=True)
709
    # fail to add a device with invalid name
710
    AssertCommand(["gnt-instance", "modify",
711
                   "--%s=-1:add,name=2%s" % (dev_type, options),
712
                   name], fail=True)
713
  # Rename disks
714
  disks = qa_config.GetDiskOptions()
715
  disk_names = [d.get("name") for d in disks]
716
  for idx, disk_name in enumerate(disk_names):
717
    # Refer to disk by idx
718
    AssertCommand(["gnt-instance", "modify",
719
                   "--disk=%s:modify,name=renamed" % idx,
720
                   name])
721
    # Refer to by name and rename to original name
722
    AssertCommand(["gnt-instance", "modify",
723
                   "--disk=renamed:modify,name=%s" % disk_name,
724
                   name])
725
  if len(disks) >= 2:
726
    # fail in renaming to disks to the same name
727
    AssertCommand(["gnt-instance", "modify",
728
                   "--disk=0:modify,name=same_name",
729
                   "--disk=1:modify,name=same_name",
730
                   name], fail=True)
731

    
732

    
733
def TestInstanceList():
734
  """gnt-instance list"""
735
  qa_utils.GenericQueryTest("gnt-instance", query.INSTANCE_FIELDS.keys())
736

    
737

    
738
def TestInstanceListFields():
739
  """gnt-instance list-fields"""
740
  qa_utils.GenericQueryFieldsTest("gnt-instance", query.INSTANCE_FIELDS.keys())
741

    
742

    
743
@InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
744
def TestInstanceConsole(instance):
745
  """gnt-instance console"""
746
  AssertCommand(["gnt-instance", "console", "--show-cmd", instance.name])
747

    
748

    
749
@InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
750
def TestReplaceDisks(instance, curr_nodes, other_nodes):
751
  """gnt-instance replace-disks"""
752
  def buildcmd(args):
753
    cmd = ["gnt-instance", "replace-disks"]
754
    cmd.extend(args)
755
    cmd.append(instance.name)
756
    return cmd
757

    
758
  if not IsDiskReplacingSupported(instance):
759
    print qa_utils.FormatInfo("Instance doesn't support disk replacing,"
760
                              " skipping test")
761
    return
762

    
763
  # Currently all supported templates have one primary and one secondary node
764
  assert len(curr_nodes) == 2
765
  snode = curr_nodes[1]
766
  assert len(other_nodes) == 1
767
  othernode = other_nodes[0]
768

    
769
  options = qa_config.get("options", {})
770
  use_ialloc = options.get("use-iallocators", True)
771
  for data in [
772
    ["-p"],
773
    ["-s"],
774
    # A placeholder; the actual command choice depends on use_ialloc
775
    None,
776
    # Restore the original secondary
777
    ["--new-secondary=%s" % snode.primary],
778
    ]:
779
    if data is None:
780
      if use_ialloc:
781
        data = ["-I", constants.DEFAULT_IALLOCATOR_SHORTCUT]
782
      else:
783
        data = ["--new-secondary=%s" % othernode.primary]
784
    AssertCommand(buildcmd(data))
785

    
786
  AssertCommand(buildcmd(["-a"]))
787
  AssertCommand(["gnt-instance", "stop", instance.name])
788
  AssertCommand(buildcmd(["-a"]), fail=True)
789
  AssertCommand(["gnt-instance", "activate-disks", instance.name])
790
  AssertCommand(["gnt-instance", "activate-disks", "--wait-for-sync",
791
                 instance.name])
792
  AssertCommand(buildcmd(["-a"]))
793
  AssertCommand(["gnt-instance", "start", instance.name])
794

    
795

    
796
def _AssertRecreateDisks(cmdargs, instance, fail=False, check=True,
797
                         destroy=True):
798
  """Execute gnt-instance recreate-disks and check the result
799

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

806
  """
807
  if destroy:
808
    _DestroyInstanceDisks(instance)
809
  AssertCommand((["gnt-instance", "recreate-disks"] + cmdargs +
810
                 [instance.name]), fail)
811
  if not fail and check:
812
    # Quick check that the disks are there
813
    AssertCommand(["gnt-instance", "activate-disks", instance.name])
814
    AssertCommand(["gnt-instance", "activate-disks", "--wait-for-sync",
815
                   instance.name])
816
    AssertCommand(["gnt-instance", "deactivate-disks", instance.name])
817

    
818

    
819
def _BuildRecreateDisksOpts(en_disks, with_spindles, with_growth,
820
                            spindles_supported):
821
  if with_spindles:
822
    if spindles_supported:
823
      if with_growth:
824
        build_spindles_opt = (lambda disk:
825
                              ",spindles=%s" %
826
                              (disk["spindles"] + disk["spindles-growth"]))
827
      else:
828
        build_spindles_opt = (lambda disk:
829
                              ",spindles=%s" % disk["spindles"])
830
    else:
831
      build_spindles_opt = (lambda _: ",spindles=1")
832
  else:
833
    build_spindles_opt = (lambda _: "")
834
  if with_growth:
835
    build_size_opt = (lambda disk:
836
                      "size=%s" % (utils.ParseUnit(disk["size"]) +
837
                                   utils.ParseUnit(disk["growth"])))
838
  else:
839
    build_size_opt = (lambda disk: "size=%s" % disk["size"])
840
  build_disk_opt = (lambda (idx, disk):
841
                    "--disk=%s:%s%s" % (idx, build_size_opt(disk),
842
                                        build_spindles_opt(disk)))
843
  return map(build_disk_opt, en_disks)
844

    
845

    
846
@InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
847
def TestRecreateDisks(instance, inodes, othernodes):
848
  """gnt-instance recreate-disks
849

850
  @param instance: Instance to work on
851
  @param inodes: List of the current nodes of the instance
852
  @param othernodes: list/tuple of nodes where to temporarily recreate disks
853

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

    
914

    
915
@InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
916
def TestInstanceExport(instance, node):
917
  """gnt-backup export -n ..."""
918
  name = instance.name
919
  AssertCommand(["gnt-backup", "export", "-n", node.primary, name])
920
  return qa_utils.ResolveInstanceName(name)
921

    
922

    
923
@InstanceCheck(None, INST_DOWN, FIRST_ARG)
924
def TestInstanceExportWithRemove(instance, node):
925
  """gnt-backup export --remove-instance"""
926
  AssertCommand(["gnt-backup", "export", "-n", node.primary,
927
                 "--remove-instance", instance.name])
928

    
929

    
930
@InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
931
def TestInstanceExportNoTarget(instance):
932
  """gnt-backup export (without target node, should fail)"""
933
  AssertCommand(["gnt-backup", "export", instance.name], fail=True)
934

    
935

    
936
@InstanceCheck(None, INST_DOWN, FIRST_ARG)
937
def TestInstanceImport(newinst, node, expnode, name):
938
  """gnt-backup import"""
939
  templ = constants.DT_PLAIN
940
  cmd = (["gnt-backup", "import",
941
          "--disk-template=%s" % templ,
942
          "--no-ip-check",
943
          "--src-node=%s" % expnode.primary,
944
          "--src-dir=%s/%s" % (pathutils.EXPORT_DIR, name),
945
          "--node=%s" % node.primary] +
946
         GetGenericAddParameters(newinst, templ,
947
                                  force_mac=constants.VALUE_GENERATE))
948
  cmd.append(newinst.name)
949
  AssertCommand(cmd)
950
  newinst.SetDiskTemplate(templ)
951

    
952

    
953
def TestBackupList(expnode):
954
  """gnt-backup list"""
955
  AssertCommand(["gnt-backup", "list", "--node=%s" % expnode.primary])
956

    
957
  qa_utils.GenericQueryTest("gnt-backup", query.EXPORT_FIELDS.keys(),
958
                            namefield=None, test_unknown=False)
959

    
960

    
961
def TestBackupListFields():
962
  """gnt-backup list-fields"""
963
  qa_utils.GenericQueryFieldsTest("gnt-backup", query.EXPORT_FIELDS.keys())
964

    
965

    
966
def TestRemoveInstanceOfflineNode(instance, snode, set_offline, set_online):
967
  """gnt-instance remove with an off-line node
968

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

974
  """
975
  info = GetInstanceInfo(instance.name)
976
  set_offline(snode)
977
  try:
978
    TestInstanceRemove(instance)
979
  finally:
980
    set_online(snode)
981

    
982
  # Clean up the disks on the offline node, if necessary
983
  if instance.disk_template not in constants.DTS_EXT_MIRROR:
984
    # FIXME: abstract the cleanup inside the disks
985
    if info["storage-type"] == constants.ST_LVM_VG:
986
      for minor in info["drbd-minors"][snode.primary]:
987
        # DRBD 8.3 syntax comes first, then DRBD 8.4 syntax. The 8.4 syntax
988
        # relies on the fact that we always create a resources for each minor,
989
        # and that this resources is always named resource{minor}.
990
        # As 'drbdsetup 0 down' does return success (even though that's invalid
991
        # syntax), we always have to perform both commands and ignore the
992
        # output.
993
        drbd_shutdown_cmd = \
994
          "(drbdsetup %d down >/dev/null 2>&1;" \
995
          " drbdsetup down resource%d >/dev/null 2>&1) || /bin/true" % \
996
            (minor, minor)
997
        AssertCommand(drbd_shutdown_cmd, node=snode)
998
      AssertCommand(["lvremove", "-f"] + info["volumes"], node=snode)
999
    elif info["storage-type"] == constants.ST_FILE:
1000
      filestorage = pathutils.DEFAULT_FILE_STORAGE_DIR
1001
      disk = os.path.join(filestorage, instance.name)
1002
      AssertCommand(["rm", "-rf", disk], node=snode)
1003

    
1004

    
1005
def TestInstanceCreationRestrictedByDiskTemplates():
1006
  """Test adding instances for disabled disk templates."""
1007
  if qa_config.TestEnabled("cluster-exclusive-storage"):
1008
    # These tests are valid only for non-exclusive storage
1009
    return
1010

    
1011
  enabled_disk_templates = qa_config.GetEnabledDiskTemplates()
1012
  nodes = qa_config.AcquireManyNodes(2)
1013

    
1014
  # Setup the cluster with the enabled_disk_templates
1015
  AssertCommand(
1016
    ["gnt-cluster", "modify",
1017
     "--enabled-disk-template=%s" %
1018
       ",".join(enabled_disk_templates)],
1019
    fail=False)
1020

    
1021
  # Test instance creation for enabled disk templates
1022
  for disk_template in enabled_disk_templates:
1023
    instance = CreateInstanceByDiskTemplate(nodes, disk_template, fail=False)
1024
    TestInstanceRemove(instance)
1025
    instance.Release()
1026

    
1027
  # Test that instance creation fails for disabled disk templates
1028
  disabled_disk_templates = list(constants.DISK_TEMPLATES
1029
                                 - set(enabled_disk_templates))
1030
  for disk_template in disabled_disk_templates:
1031
    instance = CreateInstanceByDiskTemplate(nodes, disk_template, fail=True)
1032

    
1033
  # Test instance creation for after disabling enabled disk templates
1034
  if (len(enabled_disk_templates) > 1):
1035
    # Partition the disk templates, enable them separately and check if the
1036
    # disabled ones cannot be used by instances.
1037
    middle = len(enabled_disk_templates) / 2
1038
    templates1 = enabled_disk_templates[:middle]
1039
    templates2 = enabled_disk_templates[middle:]
1040

    
1041
    for (enabled, disabled) in [(templates1, templates2),
1042
                                (templates2, templates1)]:
1043
      AssertCommand(["gnt-cluster", "modify",
1044
                     "--enabled-disk-template=%s" %
1045
                       ",".join(enabled)],
1046
                    fail=False)
1047
      for disk_template in disabled:
1048
        CreateInstanceByDiskTemplate(nodes, disk_template, fail=True)
1049
  elif (len(enabled_disk_templates) == 1):
1050
    # If only one disk template is enabled in the QA config, we have to enable
1051
    # some other templates in order to test if the disabling the only enabled
1052
    # disk template prohibits creating instances of that template.
1053
    other_disk_templates = list(
1054
                             set([constants.DT_DISKLESS, constants.DT_BLOCK]) -
1055
                             set(enabled_disk_templates))
1056
    AssertCommand(["gnt-cluster", "modify",
1057
                   "--enabled-disk-template=%s" %
1058
                     ",".join(other_disk_templates)],
1059
                  fail=False)
1060
    CreateInstanceByDiskTemplate(nodes, enabled_disk_templates[0], fail=True)
1061
  else:
1062
    raise qa_error.Error("Please enable at least one disk template"
1063
                         " in your QA setup.")
1064

    
1065
  # Restore initially enabled disk templates
1066
  AssertCommand(["gnt-cluster", "modify",
1067
                 "--enabled-disk-template=%s" %
1068
                   ",".join(enabled_disk_templates)],
1069
                 fail=False)