Statistics
| Branch: | Tag: | Revision:

root / qa / qa_instance.py @ d101b7be

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 qa_config.GetExclusiveStorage():
656
    print qa_utils.FormatInfo("Test not supported with exclusive_storage")
657
    return
658

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

    
663
  name = instance.name
664
  disks = qa_config.GetDiskOptions()
665
  all_size = [d.get("size") for d in disks]
666
  all_grow = [d.get("growth") for d in disks]
667

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

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

    
685

    
686
@InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
687
def TestInstanceDeviceNames(instance):
688
  if instance.disk_template == constants.DT_DISKLESS:
689
    print qa_utils.FormatInfo("Test not supported for diskless instances")
690
    return
691

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

    
736

    
737
def TestInstanceList():
738
  """gnt-instance list"""
739
  qa_utils.GenericQueryTest("gnt-instance", query.INSTANCE_FIELDS.keys())
740

    
741

    
742
def TestInstanceListFields():
743
  """gnt-instance list-fields"""
744
  qa_utils.GenericQueryFieldsTest("gnt-instance", query.INSTANCE_FIELDS.keys())
745

    
746

    
747
@InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
748
def TestInstanceConsole(instance):
749
  """gnt-instance console"""
750
  AssertCommand(["gnt-instance", "console", "--show-cmd", instance.name])
751

    
752

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

    
762
  if not IsDiskReplacingSupported(instance):
763
    print qa_utils.FormatInfo("Instance doesn't support disk replacing,"
764
                              " skipping test")
765
    return
766

    
767
  # Currently all supported templates have one primary and one secondary node
768
  assert len(curr_nodes) == 2
769
  snode = curr_nodes[1]
770
  assert len(other_nodes) == 1
771
  othernode = other_nodes[0]
772

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

    
790
  AssertCommand(buildcmd(["-a"]))
791
  AssertCommand(["gnt-instance", "stop", instance.name])
792
  AssertCommand(buildcmd(["-a"]), fail=True)
793
  AssertCommand(["gnt-instance", "activate-disks", instance.name])
794
  AssertCommand(["gnt-instance", "activate-disks", "--wait-for-sync",
795
                 instance.name])
796
  AssertCommand(buildcmd(["-a"]))
797
  AssertCommand(["gnt-instance", "start", instance.name])
798

    
799

    
800
def _AssertRecreateDisks(cmdargs, instance, fail=False, check=True,
801
                         destroy=True):
802
  """Execute gnt-instance recreate-disks and check the result
803

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

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

    
822

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

    
849

    
850
@InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
851
def TestRecreateDisks(instance, inodes, othernodes):
852
  """gnt-instance recreate-disks
853

854
  @param instance: Instance to work on
855
  @param inodes: List of the current nodes of the instance
856
  @param othernodes: list/tuple of nodes where to temporarily recreate disks
857

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

    
918

    
919
@InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
920
def TestInstanceExport(instance, node):
921
  """gnt-backup export -n ..."""
922
  name = instance.name
923
  AssertCommand(["gnt-backup", "export", "-n", node.primary, name])
924
  return qa_utils.ResolveInstanceName(name)
925

    
926

    
927
@InstanceCheck(None, INST_DOWN, FIRST_ARG)
928
def TestInstanceExportWithRemove(instance, node):
929
  """gnt-backup export --remove-instance"""
930
  AssertCommand(["gnt-backup", "export", "-n", node.primary,
931
                 "--remove-instance", instance.name])
932

    
933

    
934
@InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
935
def TestInstanceExportNoTarget(instance):
936
  """gnt-backup export (without target node, should fail)"""
937
  AssertCommand(["gnt-backup", "export", instance.name], fail=True)
938

    
939

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

    
956

    
957
def TestBackupList(expnode):
958
  """gnt-backup list"""
959
  AssertCommand(["gnt-backup", "list", "--node=%s" % expnode.primary])
960

    
961
  qa_utils.GenericQueryTest("gnt-backup", query.EXPORT_FIELDS.keys(),
962
                            namefield=None, test_unknown=False)
963

    
964

    
965
def TestBackupListFields():
966
  """gnt-backup list-fields"""
967
  qa_utils.GenericQueryFieldsTest("gnt-backup", query.EXPORT_FIELDS.keys())
968

    
969

    
970
def TestRemoveInstanceOfflineNode(instance, snode, set_offline, set_online):
971
  """gnt-instance remove with an off-line node
972

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

978
  """
979
  info = _GetInstanceInfo(instance.name)
980
  set_offline(snode)
981
  try:
982
    TestInstanceRemove(instance)
983
  finally:
984
    set_online(snode)
985

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

    
1007

    
1008
def TestInstanceCreationRestrictedByDiskTemplates():
1009
  """Test adding instances for disabled disk templates."""
1010
  enabled_disk_templates = qa_config.GetEnabledDiskTemplates()
1011
  nodes = qa_config.AcquireManyNodes(2)
1012

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

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

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

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

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

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