Statistics
| Branch: | Tag: | Revision:

root / qa / qa_instance.py @ 345d395d

History | View | Annotate | Download (36.4 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_FILE 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
  size = qa_config.GetDiskOptions()[-1].get("size")
634
  name = instance.name
635
  build_cmd = lambda arg: ["gnt-instance", "modify", "--disk", arg, name]
636
  AssertCommand(build_cmd("add:size=%s" % size))
637
  AssertCommand(build_cmd("remove"))
638

    
639

    
640
@InstanceCheck(INST_DOWN, INST_DOWN, FIRST_ARG)
641
def TestInstanceGrowDisk(instance):
642
  """gnt-instance grow-disk"""
643
  if qa_config.GetExclusiveStorage():
644
    print qa_utils.FormatInfo("Test not supported with exclusive_storage")
645
    return
646

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

    
651
  name = instance.name
652
  disks = qa_config.GetDiskOptions()
653
  all_size = [d.get("size") for d in disks]
654
  all_grow = [d.get("growth") for d in disks]
655

    
656
  if not all_grow:
657
    # missing disk sizes but instance grow disk has been enabled,
658
    # let's set fixed/nomimal growth
659
    all_grow = ["128M" for _ in all_size]
660

    
661
  for idx, (size, grow) in enumerate(zip(all_size, all_grow)):
662
    # succeed in grow by amount
663
    AssertCommand(["gnt-instance", "grow-disk", name, str(idx), grow])
664
    # fail in grow to the old size
665
    AssertCommand(["gnt-instance", "grow-disk", "--absolute", name, str(idx),
666
                   size], fail=True)
667
    # succeed to grow to old size + 2 * growth
668
    int_size = utils.ParseUnit(size)
669
    int_grow = utils.ParseUnit(grow)
670
    AssertCommand(["gnt-instance", "grow-disk", "--absolute", name, str(idx),
671
                   str(int_size + 2 * int_grow)])
672

    
673

    
674
@InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
675
def TestInstanceDeviceNames(instance):
676
  if instance.disk_template == constants.DT_DISKLESS:
677
    print qa_utils.FormatInfo("Test not supported for diskless instances")
678
    return
679

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

    
722

    
723
def TestInstanceList():
724
  """gnt-instance list"""
725
  qa_utils.GenericQueryTest("gnt-instance", query.INSTANCE_FIELDS.keys())
726

    
727

    
728
def TestInstanceListFields():
729
  """gnt-instance list-fields"""
730
  qa_utils.GenericQueryFieldsTest("gnt-instance", query.INSTANCE_FIELDS.keys())
731

    
732

    
733
@InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
734
def TestInstanceConsole(instance):
735
  """gnt-instance console"""
736
  AssertCommand(["gnt-instance", "console", "--show-cmd", instance.name])
737

    
738

    
739
@InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
740
def TestReplaceDisks(instance, curr_nodes, other_nodes):
741
  """gnt-instance replace-disks"""
742
  def buildcmd(args):
743
    cmd = ["gnt-instance", "replace-disks"]
744
    cmd.extend(args)
745
    cmd.append(instance.name)
746
    return cmd
747

    
748
  if not IsDiskReplacingSupported(instance):
749
    print qa_utils.FormatInfo("Instance doesn't support disk replacing,"
750
                              " skipping test")
751
    return
752

    
753
  # Currently all supported templates have one primary and one secondary node
754
  assert len(curr_nodes) == 2
755
  snode = curr_nodes[1]
756
  assert len(other_nodes) == 1
757
  othernode = other_nodes[0]
758

    
759
  options = qa_config.get("options", {})
760
  use_ialloc = options.get("use-iallocators", True)
761
  for data in [
762
    ["-p"],
763
    ["-s"],
764
    # A placeholder; the actual command choice depends on use_ialloc
765
    None,
766
    # Restore the original secondary
767
    ["--new-secondary=%s" % snode.primary],
768
    ]:
769
    if data is None:
770
      if use_ialloc:
771
        data = ["-I", constants.DEFAULT_IALLOCATOR_SHORTCUT]
772
      else:
773
        data = ["--new-secondary=%s" % othernode.primary]
774
    AssertCommand(buildcmd(data))
775

    
776
  AssertCommand(buildcmd(["-a"]))
777
  AssertCommand(["gnt-instance", "stop", instance.name])
778
  AssertCommand(buildcmd(["-a"]), fail=True)
779
  AssertCommand(["gnt-instance", "activate-disks", instance.name])
780
  AssertCommand(["gnt-instance", "activate-disks", "--wait-for-sync",
781
                 instance.name])
782
  AssertCommand(buildcmd(["-a"]))
783
  AssertCommand(["gnt-instance", "start", instance.name])
784

    
785

    
786
def _AssertRecreateDisks(cmdargs, instance, fail=False, check=True,
787
                         destroy=True):
788
  """Execute gnt-instance recreate-disks and check the result
789

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

796
  """
797
  if destroy:
798
    _DestroyInstanceDisks(instance)
799
  AssertCommand((["gnt-instance", "recreate-disks"] + cmdargs +
800
                 [instance.name]), fail)
801
  if not fail and check:
802
    # Quick check that the disks are there
803
    AssertCommand(["gnt-instance", "activate-disks", instance.name])
804
    AssertCommand(["gnt-instance", "activate-disks", "--wait-for-sync",
805
                   instance.name])
806
    AssertCommand(["gnt-instance", "deactivate-disks", instance.name])
807

    
808

    
809
@InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
810
def TestRecreateDisks(instance, inodes, othernodes):
811
  """gnt-instance recreate-disks
812

813
  @param instance: Instance to work on
814
  @param inodes: List of the current nodes of the instance
815
  @param othernodes: list/tuple of nodes where to temporarily recreate disks
816

817
  """
818
  options = qa_config.get("options", {})
819
  use_ialloc = options.get("use-iallocators", True)
820
  other_seq = ":".join([n.primary for n in othernodes])
821
  orig_seq = ":".join([n.primary for n in inodes])
822
  # These fail because the instance is running
823
  _AssertRecreateDisks(["-n", other_seq], instance, fail=True, destroy=False)
824
  if use_ialloc:
825
    _AssertRecreateDisks(["-I", "hail"], instance, fail=True, destroy=False)
826
  else:
827
    _AssertRecreateDisks(["-n", other_seq], instance, fail=True, destroy=False)
828
  AssertCommand(["gnt-instance", "stop", instance.name])
829
  # Disks exist: this should fail
830
  _AssertRecreateDisks([], instance, fail=True, destroy=False)
831
  # Unsupported spindles parameters: fail
832
  if not qa_config.AreSpindlesSupported():
833
    _AssertRecreateDisks(["--disk=0:spindles=2"], instance,
834
                         fail=True, destroy=False)
835
  # Recreate disks in place
836
  _AssertRecreateDisks([], instance)
837
  # Move disks away
838
  if use_ialloc:
839
    _AssertRecreateDisks(["-I", "hail"], instance)
840
    # Move disks somewhere else
841
    _AssertRecreateDisks(["-I", constants.DEFAULT_IALLOCATOR_SHORTCUT],
842
                         instance)
843
  else:
844
    _AssertRecreateDisks(["-n", other_seq], instance)
845
  # Move disks back
846
  _AssertRecreateDisks(["-n", orig_seq], instance)
847
  # Recreate resized disks
848
  alldisks = qa_config.GetDiskOptions()
849
  if qa_config.AreSpindlesSupported():
850
    build_disks_opt = (lambda idx, disk:
851
                       ("--disk=%s:size=%s,spindles=%s" %
852
                        (idx, (utils.ParseUnit(disk["size"]) +
853
                               utils.ParseUnit(disk["growth"])),
854
                         disk["spindles"] + disk["spindles-growth"])))
855
  else:
856
    build_disks_opt = (lambda idx, disk:
857
                       ("--disk=%s:size=%s" %
858
                        (idx, (utils.ParseUnit(disk["size"]) +
859
                               utils.ParseUnit(disk["growth"])))))
860
  disk_opts = map(build_disks_opt, range(0, len(alldisks)), (alldisks))
861
  _AssertRecreateDisks(disk_opts, instance)
862
  # Recreate the disks one by one (with the original size)
863
  if qa_config.AreSpindlesSupported():
864
    build_disks_opt = lambda idx, disk: ("--disk=%s:size=%s,spindles=%s" %
865
                                         (idx, disk["size"], disk["spindles"]))
866
  else:
867
    build_disks_opt = lambda idx, disk: ("--disk=%s:size=%s" %
868
                                         (idx, disk["size"]))
869
  for (idx, disk) in enumerate(alldisks):
870
    # Only the first call should destroy all the disk
871
    destroy = (idx == 0)
872
    _AssertRecreateDisks([build_disks_opt(idx, disk)], instance,
873
                         destroy=destroy, check=False)
874
  # This and InstanceCheck decoration check that the disks are working
875
  AssertCommand(["gnt-instance", "reinstall", "-f", instance.name])
876
  AssertCommand(["gnt-instance", "start", instance.name])
877

    
878

    
879
@InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
880
def TestInstanceExport(instance, node):
881
  """gnt-backup export -n ..."""
882
  name = instance.name
883
  AssertCommand(["gnt-backup", "export", "-n", node.primary, name])
884
  return qa_utils.ResolveInstanceName(name)
885

    
886

    
887
@InstanceCheck(None, INST_DOWN, FIRST_ARG)
888
def TestInstanceExportWithRemove(instance, node):
889
  """gnt-backup export --remove-instance"""
890
  AssertCommand(["gnt-backup", "export", "-n", node.primary,
891
                 "--remove-instance", instance.name])
892

    
893

    
894
@InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
895
def TestInstanceExportNoTarget(instance):
896
  """gnt-backup export (without target node, should fail)"""
897
  AssertCommand(["gnt-backup", "export", instance.name], fail=True)
898

    
899

    
900
@InstanceCheck(None, INST_DOWN, FIRST_ARG)
901
def TestInstanceImport(newinst, node, expnode, name):
902
  """gnt-backup import"""
903
  templ = constants.DT_PLAIN
904
  cmd = (["gnt-backup", "import",
905
          "--disk-template=%s" % templ,
906
          "--no-ip-check",
907
          "--src-node=%s" % expnode.primary,
908
          "--src-dir=%s/%s" % (pathutils.EXPORT_DIR, name),
909
          "--node=%s" % node.primary] +
910
         GetGenericAddParameters(newinst, templ,
911
                                  force_mac=constants.VALUE_GENERATE))
912
  cmd.append(newinst.name)
913
  AssertCommand(cmd)
914
  newinst.SetDiskTemplate(templ)
915

    
916

    
917
def TestBackupList(expnode):
918
  """gnt-backup list"""
919
  AssertCommand(["gnt-backup", "list", "--node=%s" % expnode.primary])
920

    
921
  qa_utils.GenericQueryTest("gnt-backup", query.EXPORT_FIELDS.keys(),
922
                            namefield=None, test_unknown=False)
923

    
924

    
925
def TestBackupListFields():
926
  """gnt-backup list-fields"""
927
  qa_utils.GenericQueryFieldsTest("gnt-backup", query.EXPORT_FIELDS.keys())
928

    
929

    
930
def TestRemoveInstanceOfflineNode(instance, snode, set_offline, set_online):
931
  """gnt-instance remove with an off-line node
932

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

938
  """
939
  info = _GetInstanceInfo(instance.name)
940
  set_offline(snode)
941
  try:
942
    TestInstanceRemove(instance)
943
  finally:
944
    set_online(snode)
945

    
946
  # Clean up the disks on the offline node, if necessary
947
  if instance.disk_template not in constants.DTS_EXT_MIRROR:
948
    # FIXME: abstract the cleanup inside the disks
949
    if info["storage-type"] == constants.ST_LVM_VG:
950
      for minor in info["drbd-minors"][snode.primary]:
951
        # DRBD 8.3 syntax comes first, then DRBD 8.4 syntax. The 8.4 syntax
952
        # relies on the fact that we always create a resources for each minor,
953
        # and that this resources is always named resource{minor}.
954
        # As 'drbdsetup 0 down' does return success (even though that's invalid
955
        # syntax), we always have to perform both commands and ignore the
956
        # output.
957
        drbd_shutdown_cmd = \
958
          "(drbdsetup %d down && drbdsetup down resource%d) || /bin/true" % \
959
            (minor, minor)
960
        AssertCommand(drbd_shutdown_cmd, node=snode)
961
      AssertCommand(["lvremove", "-f"] + info["volumes"], node=snode)
962
    elif info["storage-type"] == constants.ST_FILE:
963
      filestorage = pathutils.DEFAULT_FILE_STORAGE_DIR
964
      disk = os.path.join(filestorage, instance.name)
965
      AssertCommand(["rm", "-rf", disk], node=snode)
966

    
967

    
968
def TestInstanceCreationRestrictedByDiskTemplates():
969
  """Test adding instances for disbled disk templates."""
970
  enabled_disk_templates = qa_config.GetEnabledDiskTemplates()
971
  nodes = qa_config.AcquireManyNodes(2)
972

    
973
  # Setup the cluster with the enabled_disk_templates
974
  AssertCommand(
975
    ["gnt-cluster", "modify",
976
     "--enabled-disk-template=%s" %
977
       ",".join(enabled_disk_templates)],
978
    fail=False)
979

    
980
  # Test instance creation for enabled disk templates
981
  for disk_template in enabled_disk_templates:
982
    instance = CreateInstanceByDiskTemplate(nodes, disk_template, False)
983
    TestInstanceRemove(instance)
984

    
985
  # Test that instance creation fails for disabled disk templates
986
  disabled_disk_templates = list(constants.DISK_TEMPLATES
987
                                 - set(enabled_disk_templates))
988
  for disk_template in disabled_disk_templates:
989
    instance = CreateInstanceByDiskTemplate(nodes, disk_template, True)
990

    
991
  # Test instance creation for after disabling enabled disk templates
992
  if (len(enabled_disk_templates) > 1):
993
    # Partition the disk templates, enable them separately and check if the
994
    # disabled ones cannot be used by instances.
995
    middle = len(enabled_disk_templates) / 2
996
    templates1 = enabled_disk_templates[:middle]
997
    templates2 = enabled_disk_templates[middle:]
998

    
999
    for (enabled, disabled) in [(templates1, templates2),
1000
                                (templates2, templates1)]:
1001
      AssertCommand(["gnt-cluster", "modify",
1002
                     "--enabled-disk-template=%s" %
1003
                       ",".join(enabled)],
1004
                    fail=False)
1005
      for disk_template in disabled:
1006
        CreateInstanceByDiskTemplate(nodes, disk_template, True)
1007
  elif (len(enabled_disk_templates) == 1):
1008
    # If only one disk template is enabled in the QA config, we have to enable
1009
    # some of the disabled disk templates in order to test if the disabling the
1010
    # only enabled disk template prohibits creating instances of that template.
1011
    AssertCommand(["gnt-cluster", "modify",
1012
                   "--enabled-disk-template=%s" %
1013
                     ",".join(disabled_disk_templates)],
1014
                  fail=False)
1015
    CreateInstanceByDiskTemplate(nodes, enabled_disk_templates[0], True)
1016
  else:
1017
    raise qa_error.Error("Please enable at least one disk template"
1018
                         " in your QA setup.")
1019

    
1020
  # Restore initially enabled disk templates
1021
  AssertCommand(["gnt-cluster", "modify",
1022
                 "--enabled-disk-template=%s" %
1023
                   ",".join(enabled_disk_templates)],
1024
                 fail=False)