Statistics
| Branch: | Tag: | Revision:

root / qa / qa_instance.py @ 5949c31c

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
    # Note that this works for both file and sharedfile, and this is intended.
140
    storage_dir = qa_config.get("file-storage-dir",
141
                                pathutils.DEFAULT_FILE_STORAGE_DIR)
142
    idir = os.path.join(storage_dir, instance.name)
143
    for node in info["nodes"]:
144
      AssertCommand(["rm", "-rf", idir], node=node)
145
  elif info["storage-type"] == constants.ST_DISKLESS:
146
    pass
147

    
148

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

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

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

    
164

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

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

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

    
184

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

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

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

    
206

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

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

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

    
237

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

    
241

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

    
245

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

    
249

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

    
253

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

    
263

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

    
270

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

    
278

    
279
@InstanceCheck(None, INST_UP, RETURN_VALUE)
280
def 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
  filestorage = qa_config.get("file-storage-dir",
562
                              pathutils.DEFAULT_FILE_STORAGE_DIR)
563
  disk = os.path.join(filestorage, name)
564

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

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

    
582

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

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

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

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

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

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

    
607

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

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

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

    
624

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

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

    
650

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

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

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

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

    
680

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

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

    
731

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

    
736

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

    
741

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

    
747

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

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

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

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

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

    
794

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

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

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

    
817

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

    
844

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

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

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

    
913

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

    
921

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

    
928

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

    
934

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

    
951

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

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

    
959

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

    
964

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

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

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

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