Statistics
| Branch: | Tag: | Revision:

root / qa / qa_instance.py @ cb178a1e

History | View | Annotate | Download (38.9 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 operator
27
import os
28
import re
29

    
30
from ganeti import utils
31
from ganeti import constants
32
from ganeti import query
33
from ganeti import pathutils
34

    
35
import qa_config
36
import qa_utils
37
import qa_error
38

    
39
from qa_utils import AssertIn, AssertCommand, AssertEqual
40
from qa_utils import InstanceCheck, INST_DOWN, INST_UP, FIRST_ARG, RETURN_VALUE
41

    
42

    
43
def _GetDiskStatePath(disk):
44
  return "/sys/block/%s/device/state" % disk
45

    
46

    
47
def _GetGenericAddParameters(inst, disk_template, force_mac=None):
48
  params = ["-B"]
49
  params.append("%s=%s,%s=%s" % (constants.BE_MINMEM,
50
                                 qa_config.get(constants.BE_MINMEM),
51
                                 constants.BE_MAXMEM,
52
                                 qa_config.get(constants.BE_MAXMEM)))
53

    
54
  if disk_template != constants.DT_DISKLESS:
55
    for idx, disk in enumerate(qa_config.GetDiskOptions()):
56
      size = disk.get("size")
57
      name = disk.get("name")
58
      diskparams = "%s:size=%s" % (idx, size)
59
      if name:
60
        diskparams += ",name=%s" % name
61
      params.extend(["--disk", diskparams])
62

    
63
  # Set static MAC address if configured
64
  if force_mac:
65
    nic0_mac = force_mac
66
  else:
67
    nic0_mac = inst.GetNicMacAddr(0, None)
68

    
69
  if nic0_mac:
70
    params.extend(["--net", "0:mac=%s" % nic0_mac])
71

    
72
  return params
73

    
74

    
75
def _CreateInstanceByDiskTemplateRaw(nodes_spec, disk_template, fail=False):
76
  """Creates an instance with the given disk template on the given nodes(s).
77
     Note that this function does not check if enough nodes are given for
78
     the respective disk template.
79

80
  @type nodes_spec: string
81
  @param nodes_spec: string specification of one node (by node name) or several
82
                     nodes according to the requirements of the disk template
83
  @type disk_template: string
84
  @param disk_template: the disk template to be used by the instance
85
  @return: the created instance
86

87
  """
88
  instance = qa_config.AcquireInstance()
89
  try:
90
    cmd = (["gnt-instance", "add",
91
            "--os-type=%s" % qa_config.get("os"),
92
            "--disk-template=%s" % disk_template,
93
            "--node=%s" % nodes_spec] +
94
           _GetGenericAddParameters(instance, disk_template))
95
    cmd.append(instance.name)
96

    
97
    AssertCommand(cmd, fail=fail)
98

    
99
    if not fail:
100
      _CheckSsconfInstanceList(instance.name)
101
      instance.SetDiskTemplate(disk_template)
102

    
103
      return instance
104
  except:
105
    instance.Release()
106
    raise
107

    
108
  # Handle the case where creation is expected to fail
109
  assert fail
110
  instance.Release()
111
  return None
112

    
113

    
114
def _CreateInstanceByDiskTemplateOneNode(nodes, disk_template, fail=False):
115
  """Creates an instance using the given disk template for disk templates
116
     for which one given node is sufficient. These templates are for example:
117
     plain, diskless, file, sharedfile, blockdev, rados.
118

119
  @type nodes: list of nodes
120
  @param nodes: a list of nodes, whose first element is used to create the
121
                instance
122
  @type disk_template: string
123
  @param disk_template: the disk template to be used by the instance
124
  @return: the created instance
125

126
  """
127
  assert len(nodes) > 0
128
  return _CreateInstanceByDiskTemplateRaw(nodes[0].primary, disk_template,
129
                                          fail=fail)
130

    
131

    
132
def _CreateInstanceDrbd8(nodes, fail=False):
133
  """Creates an instance using disk template 'drbd' on the given nodes.
134

135
  @type nodes: list of nodes
136
  @param nodes: nodes to be used by the instance
137
  @return: the created instance
138

139
  """
140
  assert len(nodes) > 1
141
  return _CreateInstanceByDiskTemplateRaw(
142
    ":".join(map(operator.attrgetter("primary"), nodes)),
143
    constants.DT_DRBD8, fail=fail)
144

    
145

    
146
def CreateInstanceByDiskTemplate(nodes, disk_template, fail=False):
147
  """Given a disk template, this function creates an instance using
148
     the template. It uses the required number of nodes depending on
149
     the disk template. This function is intended to be used by tests
150
     that don't care about the specifics of the instance other than
151
     that it uses the given disk template.
152

153
     Note: If you use this function, make sure to call
154
     'TestInstanceRemove' at the end of your tests to avoid orphaned
155
     instances hanging around and interfering with the following tests.
156

157
  @type nodes: list of nodes
158
  @param nodes: the list of the nodes on which the instance will be placed;
159
                it needs to have sufficiently many elements for the given
160
                disk template
161
  @type disk_template: string
162
  @param disk_template: the disk template to be used by the instance
163
  @return: the created instance
164

165
  """
166
  if disk_template == constants.DT_DRBD8:
167
    return _CreateInstanceDrbd8(nodes, fail=fail)
168
  elif disk_template in [constants.DT_DISKLESS, constants.DT_PLAIN,
169
                         constants.DT_FILE]:
170
    return _CreateInstanceByDiskTemplateOneNode(nodes, disk_template, fail=fail)
171
  else:
172
    # FIXME: This assumes that for all other disk templates, we only need one
173
    # node and no disk template specific parameters. This else-branch is
174
    # currently only used in cases where we expect failure. Extend it when
175
    # QA needs for these templates change.
176
    return _CreateInstanceByDiskTemplateOneNode(nodes, disk_template, fail=fail)
177

    
178

    
179
def _GetInstanceInfo(instance):
180
  """Return information about the actual state of an instance.
181

182
  @type instance: string
183
  @param instance: the instance name
184
  @return: a dictionary with the following keys:
185
      - "nodes": instance nodes, a list of strings
186
      - "volumes": instance volume IDs, a list of strings
187
      - "drbd-minors": DRBD minors used by the instance, a dictionary where
188
        keys are nodes, and values are lists of integers (or an empty
189
        dictionary for non-DRBD instances)
190
      - "disk-template": instance disk template
191
      - "storage-type": storage type associated with the instance disk template
192

193
  """
194
  node_elem = r"([^,()]+)(?:\s+\([^)]+\))?"
195
  # re_nodelist matches a list of nodes returned by gnt-instance info, e.g.:
196
  #  node1.fqdn
197
  #  node2.fqdn,node3.fqdn
198
  #  node4.fqdn (group mygroup, group UUID 01234567-abcd-0123-4567-0123456789ab)
199
  # FIXME This works with no more than 2 secondaries
200
  re_nodelist = re.compile(node_elem + "(?:," + node_elem + ")?$")
201

    
202
  info = qa_utils.GetObjectInfo(["gnt-instance", "info", instance])[0]
203
  nodes = []
204
  for nodeinfo in info["Nodes"]:
205
    if "primary" in nodeinfo:
206
      nodes.append(nodeinfo["primary"])
207
    elif "secondaries" in nodeinfo:
208
      nodestr = nodeinfo["secondaries"]
209
      if nodestr:
210
        m = re_nodelist.match(nodestr)
211
        if m:
212
          nodes.extend(filter(None, m.groups()))
213
        else:
214
          nodes.append(nodestr)
215

    
216
  disk_template = info["Disk template"]
217
  if not disk_template:
218
    raise qa_error.Error("Can't get instance disk template")
219
  storage_type = constants.DISK_TEMPLATES_STORAGE_TYPE[disk_template]
220

    
221
  re_drbdnode = re.compile(r"^([^\s,]+),\s+minor=([0-9]+)$")
222
  vols = []
223
  drbd_min = {}
224
  for (count, diskinfo) in enumerate(info["Disks"]):
225
    (dtype, _) = diskinfo["disk/%s" % count].split(",", 1)
226
    if dtype == constants.LD_DRBD8:
227
      for child in diskinfo["child devices"]:
228
        vols.append(child["logical_id"])
229
      for key in ["nodeA", "nodeB"]:
230
        m = re_drbdnode.match(diskinfo[key])
231
        if not m:
232
          raise qa_error.Error("Cannot parse DRBD info: %s" % diskinfo[key])
233
        node = m.group(1)
234
        minor = int(m.group(2))
235
        minorlist = drbd_min.setdefault(node, [])
236
        minorlist.append(minor)
237
    elif dtype == constants.LD_LV:
238
      vols.append(diskinfo["logical_id"])
239

    
240
  assert nodes
241
  assert len(nodes) < 2 or vols
242
  return {
243
    "nodes": nodes,
244
    "volumes": vols,
245
    "drbd-minors": drbd_min,
246
    "disk-template": disk_template,
247
    "storage-type": storage_type,
248
    }
249

    
250

    
251
def _DestroyInstanceDisks(instance):
252
  """Remove all the backend disks of an instance.
253

254
  This is used to simulate HW errors (dead nodes, broken disks...); the
255
  configuration of the instance is not affected.
256
  @type instance: dictionary
257
  @param instance: the instance
258

259
  """
260
  info = _GetInstanceInfo(instance.name)
261
  # FIXME: destruction/removal should be part of the disk class
262
  if info["storage-type"] == constants.ST_LVM_VG:
263
    vols = info["volumes"]
264
    for node in info["nodes"]:
265
      AssertCommand(["lvremove", "-f"] + vols, node=node)
266
  elif info["storage-type"] == constants.ST_FILE:
267
    # FIXME: file storage dir not configurable in qa
268
    # Note that this works for both file and sharedfile, and this is intended.
269
    filestorage = pathutils.DEFAULT_FILE_STORAGE_DIR
270
    idir = os.path.join(filestorage, instance.name)
271
    for node in info["nodes"]:
272
      AssertCommand(["rm", "-rf", idir], node=node)
273
  elif info["storage-type"] == constants.ST_DISKLESS:
274
    pass
275

    
276

    
277
def _GetInstanceField(instance, field):
278
  """Get the value of a field of an instance.
279

280
  @type instance: string
281
  @param instance: Instance name
282
  @type field: string
283
  @param field: Name of the field
284
  @rtype: string
285

286
  """
287
  master = qa_config.GetMasterNode()
288
  infocmd = utils.ShellQuoteArgs(["gnt-instance", "list", "--no-headers",
289
                                  "--units", "m", "-o", field, instance])
290
  return qa_utils.GetCommandOutput(master.primary, infocmd).strip()
291

    
292

    
293
def _GetBoolInstanceField(instance, field):
294
  """Get the Boolean value of a field of an instance.
295

296
  @type instance: string
297
  @param instance: Instance name
298
  @type field: string
299
  @param field: Name of the field
300
  @rtype: bool
301

302
  """
303
  info_out = _GetInstanceField(instance, field)
304
  if info_out == "Y":
305
    return True
306
  elif info_out == "N":
307
    return False
308
  else:
309
    raise qa_error.Error("Field %s of instance %s has a non-Boolean value:"
310
                         " %s" % (field, instance, info_out))
311

    
312

    
313
def _GetNumInstanceField(instance, field):
314
  """Get a numeric value of a field of an instance.
315

316
  @type instance: string
317
  @param instance: Instance name
318
  @type field: string
319
  @param field: Name of the field
320
  @rtype: int or float
321

322
  """
323
  info_out = _GetInstanceField(instance, field)
324
  try:
325
    ret = int(info_out)
326
  except ValueError:
327
    try:
328
      ret = float(info_out)
329
    except ValueError:
330
      raise qa_error.Error("Field %s of instance %s has a non-numeric value:"
331
                           " %s" % (field, instance, info_out))
332
  return ret
333

    
334

    
335
def GetInstanceSpec(instance, spec):
336
  """Return the current spec for the given parameter.
337

338
  @type instance: string
339
  @param instance: Instance name
340
  @type spec: string
341
  @param spec: one of the supported parameters: "memory-size", "cpu-count",
342
      "disk-count", "disk-size", "nic-count"
343
  @rtype: tuple
344
  @return: (minspec, maxspec); minspec and maxspec can be different only for
345
      memory and disk size
346

347
  """
348
  specmap = {
349
    "memory-size": ["be/minmem", "be/maxmem"],
350
    "cpu-count": ["vcpus"],
351
    "disk-count": ["disk.count"],
352
    "disk-size": ["disk.size/ "],
353
    "nic-count": ["nic.count"],
354
    }
355
  # For disks, first we need the number of disks
356
  if spec == "disk-size":
357
    (numdisk, _) = GetInstanceSpec(instance, "disk-count")
358
    fields = ["disk.size/%s" % k for k in range(0, numdisk)]
359
  else:
360
    assert spec in specmap, "%s not in %s" % (spec, specmap)
361
    fields = specmap[spec]
362
  values = [_GetNumInstanceField(instance, f) for f in fields]
363
  return (min(values), max(values))
364

    
365

    
366
def IsFailoverSupported(instance):
367
  return instance.disk_template in constants.DTS_MIRRORED
368

    
369

    
370
def IsMigrationSupported(instance):
371
  return instance.disk_template in constants.DTS_MIRRORED
372

    
373

    
374
def IsDiskReplacingSupported(instance):
375
  return instance.disk_template == constants.DT_DRBD8
376

    
377

    
378
def TestInstanceAddWithPlainDisk(nodes, fail=False):
379
  """gnt-instance add -t plain"""
380
  if constants.DT_PLAIN in qa_config.GetEnabledDiskTemplates():
381
    instance = _CreateInstanceByDiskTemplateOneNode(nodes, constants.DT_PLAIN,
382
                                                    fail=fail)
383
    if not fail:
384
      qa_utils.RunInstanceCheck(instance, True)
385
    return instance
386

    
387

    
388
@InstanceCheck(None, INST_UP, RETURN_VALUE)
389
def TestInstanceAddWithDrbdDisk(nodes):
390
  """gnt-instance add -t drbd"""
391
  if constants.DT_DRBD8 in qa_config.GetEnabledDiskTemplates():
392
    return _CreateInstanceDrbd8(nodes)
393

    
394

    
395
@InstanceCheck(None, INST_UP, RETURN_VALUE)
396
def TestInstanceAddFile(nodes):
397
  """gnt-instance add -t file"""
398
  assert len(nodes) == 1
399
  if constants.DT_FILE in qa_config.GetEnabledDiskTemplates():
400
    return _CreateInstanceByDiskTemplateOneNode(nodes, constants.DT_FILE)
401

    
402

    
403
@InstanceCheck(None, INST_UP, RETURN_VALUE)
404
def TestInstanceAddDiskless(nodes):
405
  """gnt-instance add -t diskless"""
406
  assert len(nodes) == 1
407
  if constants.DT_FILE in qa_config.GetEnabledDiskTemplates():
408
    return _CreateInstanceByDiskTemplateOneNode(nodes, constants.DT_DISKLESS)
409

    
410

    
411
@InstanceCheck(None, INST_DOWN, FIRST_ARG)
412
def TestInstanceRemove(instance):
413
  """gnt-instance remove"""
414
  AssertCommand(["gnt-instance", "remove", "-f", instance.name])
415

    
416

    
417
@InstanceCheck(INST_DOWN, INST_UP, FIRST_ARG)
418
def TestInstanceStartup(instance):
419
  """gnt-instance startup"""
420
  AssertCommand(["gnt-instance", "startup", instance.name])
421

    
422

    
423
@InstanceCheck(INST_UP, INST_DOWN, FIRST_ARG)
424
def TestInstanceShutdown(instance):
425
  """gnt-instance shutdown"""
426
  AssertCommand(["gnt-instance", "shutdown", instance.name])
427

    
428

    
429
@InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
430
def TestInstanceReboot(instance):
431
  """gnt-instance reboot"""
432
  options = qa_config.get("options", {})
433
  reboot_types = options.get("reboot-types", constants.REBOOT_TYPES)
434
  name = instance.name
435
  for rtype in reboot_types:
436
    AssertCommand(["gnt-instance", "reboot", "--type=%s" % rtype, name])
437

    
438
  AssertCommand(["gnt-instance", "shutdown", name])
439
  qa_utils.RunInstanceCheck(instance, False)
440
  AssertCommand(["gnt-instance", "reboot", name])
441

    
442
  master = qa_config.GetMasterNode()
443
  cmd = ["gnt-instance", "list", "--no-headers", "-o", "status", name]
444
  result_output = qa_utils.GetCommandOutput(master.primary,
445
                                            utils.ShellQuoteArgs(cmd))
446
  AssertEqual(result_output.strip(), constants.INSTST_RUNNING)
447

    
448

    
449
@InstanceCheck(INST_DOWN, INST_DOWN, FIRST_ARG)
450
def TestInstanceReinstall(instance):
451
  """gnt-instance reinstall"""
452
  if instance.disk_template == constants.DT_DISKLESS:
453
    print qa_utils.FormatInfo("Test not supported for diskless instances")
454
    return
455

    
456
  AssertCommand(["gnt-instance", "reinstall", "-f", instance.name])
457

    
458
  # Test with non-existant OS definition
459
  AssertCommand(["gnt-instance", "reinstall", "-f",
460
                 "--os-type=NonExistantOsForQa",
461
                 instance.name],
462
                fail=True)
463

    
464

    
465
def _ReadSsconfInstanceList():
466
  """Reads ssconf_instance_list from the master node.
467

468
  """
469
  master = qa_config.GetMasterNode()
470

    
471
  ssconf_path = utils.PathJoin(pathutils.DATA_DIR,
472
                               "ssconf_%s" % constants.SS_INSTANCE_LIST)
473

    
474
  cmd = ["cat", qa_utils.MakeNodePath(master, ssconf_path)]
475

    
476
  return qa_utils.GetCommandOutput(master.primary,
477
                                   utils.ShellQuoteArgs(cmd)).splitlines()
478

    
479

    
480
def _CheckSsconfInstanceList(instance):
481
  """Checks if a certain instance is in the ssconf instance list.
482

483
  @type instance: string
484
  @param instance: Instance name
485

486
  """
487
  AssertIn(qa_utils.ResolveInstanceName(instance),
488
           _ReadSsconfInstanceList())
489

    
490

    
491
@InstanceCheck(INST_DOWN, INST_DOWN, FIRST_ARG)
492
def TestInstanceRenameAndBack(rename_source, rename_target):
493
  """gnt-instance rename
494

495
  This must leave the instance with the original name, not the target
496
  name.
497

498
  """
499
  _CheckSsconfInstanceList(rename_source)
500

    
501
  # first do a rename to a different actual name, expecting it to fail
502
  qa_utils.AddToEtcHosts(["meeeeh-not-exists", rename_target])
503
  try:
504
    AssertCommand(["gnt-instance", "rename", rename_source, rename_target],
505
                  fail=True)
506
    _CheckSsconfInstanceList(rename_source)
507
  finally:
508
    qa_utils.RemoveFromEtcHosts(["meeeeh-not-exists", rename_target])
509

    
510
  info = _GetInstanceInfo(rename_source)
511

    
512
  # Check instance volume tags correctly updated. Note that this check is lvm
513
  # specific, so we skip it for non-lvm-based instances.
514
  # FIXME: This will need updating when instances will be able to have
515
  # different disks living on storage pools with etherogeneous storage types.
516
  # FIXME: This check should be put inside the disk/storage class themselves,
517
  # rather than explicitly called here.
518
  if info["storage-type"] == constants.ST_LVM_VG:
519
    # In the lvm world we can check for tags on the logical volume
520
    tags_cmd = ("lvs -o tags --noheadings %s | grep " %
521
                (" ".join(info["volumes"]), ))
522
  else:
523
    # Other storage types don't have tags, so we use an always failing command,
524
    # to make sure it never gets executed
525
    tags_cmd = "false"
526

    
527
  # and now rename instance to rename_target...
528
  AssertCommand(["gnt-instance", "rename", rename_source, rename_target])
529
  _CheckSsconfInstanceList(rename_target)
530
  qa_utils.RunInstanceCheck(rename_source, False)
531
  qa_utils.RunInstanceCheck(rename_target, False)
532

    
533
  # NOTE: tags might not be the exactly as the instance name, due to
534
  # charset restrictions; hence the test might be flaky
535
  if (rename_source != rename_target and
536
      info["storage-type"] == constants.ST_LVM_VG):
537
    for node in info["nodes"]:
538
      AssertCommand(tags_cmd + rename_source, node=node, fail=True)
539
      AssertCommand(tags_cmd + rename_target, node=node, fail=False)
540

    
541
  # and back
542
  AssertCommand(["gnt-instance", "rename", rename_target, rename_source])
543
  _CheckSsconfInstanceList(rename_source)
544
  qa_utils.RunInstanceCheck(rename_target, False)
545

    
546
  if (rename_source != rename_target and
547
      info["storage-type"] == constants.ST_LVM_VG):
548
    for node in info["nodes"]:
549
      AssertCommand(tags_cmd + rename_source, node=node, fail=False)
550
      AssertCommand(tags_cmd + rename_target, node=node, fail=True)
551

    
552

    
553
@InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
554
def TestInstanceFailover(instance):
555
  """gnt-instance failover"""
556
  if not IsFailoverSupported(instance):
557
    print qa_utils.FormatInfo("Instance doesn't support failover, skipping"
558
                              " test")
559
    return
560

    
561
  cmd = ["gnt-instance", "failover", "--force", instance.name]
562

    
563
  # failover ...
564
  AssertCommand(cmd)
565
  qa_utils.RunInstanceCheck(instance, True)
566

    
567
  # ... and back
568
  AssertCommand(cmd)
569

    
570

    
571
@InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
572
def TestInstanceMigrate(instance, toggle_always_failover=True):
573
  """gnt-instance migrate"""
574
  if not IsMigrationSupported(instance):
575
    print qa_utils.FormatInfo("Instance doesn't support migration, skipping"
576
                              " test")
577
    return
578

    
579
  cmd = ["gnt-instance", "migrate", "--force", instance.name]
580
  af_par = constants.BE_ALWAYS_FAILOVER
581
  af_field = "be/" + constants.BE_ALWAYS_FAILOVER
582
  af_init_val = _GetBoolInstanceField(instance.name, af_field)
583

    
584
  # migrate ...
585
  AssertCommand(cmd)
586
  # TODO: Verify the choice between failover and migration
587
  qa_utils.RunInstanceCheck(instance, True)
588

    
589
  # ... and back (possibly with always_failover toggled)
590
  if toggle_always_failover:
591
    AssertCommand(["gnt-instance", "modify", "-B",
592
                   ("%s=%s" % (af_par, not af_init_val)),
593
                   instance.name])
594
  AssertCommand(cmd)
595
  # TODO: Verify the choice between failover and migration
596
  qa_utils.RunInstanceCheck(instance, True)
597
  if toggle_always_failover:
598
    AssertCommand(["gnt-instance", "modify", "-B",
599
                   ("%s=%s" % (af_par, af_init_val)), instance.name])
600

    
601
  # TODO: Split into multiple tests
602
  AssertCommand(["gnt-instance", "shutdown", instance.name])
603
  qa_utils.RunInstanceCheck(instance, False)
604
  AssertCommand(cmd, fail=True)
605
  AssertCommand(["gnt-instance", "migrate", "--force", "--allow-failover",
606
                 instance.name])
607
  AssertCommand(["gnt-instance", "start", instance.name])
608
  AssertCommand(cmd)
609
  # @InstanceCheck enforces the check that the instance is running
610
  qa_utils.RunInstanceCheck(instance, True)
611

    
612
  AssertCommand(["gnt-instance", "modify", "-B",
613
                 ("%s=%s" %
614
                  (constants.BE_ALWAYS_FAILOVER, constants.VALUE_TRUE)),
615
                 instance.name])
616

    
617
  AssertCommand(cmd)
618
  qa_utils.RunInstanceCheck(instance, True)
619
  # TODO: Verify that a failover has been done instead of a migration
620

    
621
  # TODO: Verify whether the default value is restored here (not hardcoded)
622
  AssertCommand(["gnt-instance", "modify", "-B",
623
                 ("%s=%s" %
624
                  (constants.BE_ALWAYS_FAILOVER, constants.VALUE_FALSE)),
625
                 instance.name])
626

    
627
  AssertCommand(cmd)
628
  qa_utils.RunInstanceCheck(instance, True)
629

    
630

    
631
def TestInstanceInfo(instance):
632
  """gnt-instance info"""
633
  AssertCommand(["gnt-instance", "info", instance.name])
634

    
635

    
636
@InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
637
def TestInstanceModify(instance):
638
  """gnt-instance modify"""
639
  default_hv = qa_config.GetDefaultHypervisor()
640

    
641
  # Assume /sbin/init exists on all systems
642
  test_kernel = "/sbin/init"
643
  test_initrd = test_kernel
644

    
645
  orig_maxmem = qa_config.get(constants.BE_MAXMEM)
646
  orig_minmem = qa_config.get(constants.BE_MINMEM)
647
  #orig_bridge = qa_config.get("bridge", "xen-br0")
648

    
649
  args = [
650
    ["-B", "%s=128" % constants.BE_MINMEM],
651
    ["-B", "%s=128" % constants.BE_MAXMEM],
652
    ["-B", "%s=%s,%s=%s" % (constants.BE_MINMEM, orig_minmem,
653
                            constants.BE_MAXMEM, orig_maxmem)],
654
    ["-B", "%s=2" % constants.BE_VCPUS],
655
    ["-B", "%s=1" % constants.BE_VCPUS],
656
    ["-B", "%s=%s" % (constants.BE_VCPUS, constants.VALUE_DEFAULT)],
657
    ["-B", "%s=%s" % (constants.BE_ALWAYS_FAILOVER, constants.VALUE_TRUE)],
658
    ["-B", "%s=%s" % (constants.BE_ALWAYS_FAILOVER, constants.VALUE_DEFAULT)],
659

    
660
    ["-H", "%s=%s" % (constants.HV_KERNEL_PATH, test_kernel)],
661
    ["-H", "%s=%s" % (constants.HV_KERNEL_PATH, constants.VALUE_DEFAULT)],
662

    
663
    # TODO: bridge tests
664
    #["--bridge", "xen-br1"],
665
    #["--bridge", orig_bridge],
666
    ]
667

    
668
  if default_hv == constants.HT_XEN_PVM:
669
    args.extend([
670
      ["-H", "%s=%s" % (constants.HV_INITRD_PATH, test_initrd)],
671
      ["-H", "no_%s" % (constants.HV_INITRD_PATH, )],
672
      ["-H", "%s=%s" % (constants.HV_INITRD_PATH, constants.VALUE_DEFAULT)],
673
      ])
674
  elif default_hv == constants.HT_XEN_HVM:
675
    args.extend([
676
      ["-H", "%s=acn" % constants.HV_BOOT_ORDER],
677
      ["-H", "%s=%s" % (constants.HV_BOOT_ORDER, constants.VALUE_DEFAULT)],
678
      ])
679

    
680
  for alist in args:
681
    AssertCommand(["gnt-instance", "modify"] + alist + [instance.name])
682

    
683
  # check no-modify
684
  AssertCommand(["gnt-instance", "modify", instance.name], fail=True)
685

    
686
  # Marking offline while instance is running must fail...
687
  AssertCommand(["gnt-instance", "modify", "--offline", instance.name],
688
                 fail=True)
689

    
690
  # ...while making it online is ok, and should work
691
  AssertCommand(["gnt-instance", "modify", "--online", instance.name])
692

    
693

    
694
@InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
695
def TestInstanceModifyPrimaryAndBack(instance, currentnode, othernode):
696
  """gnt-instance modify --new-primary
697

698
  This will leave the instance on its original primary node, not other node.
699

700
  """
701
  if instance.disk_template != constants.DT_FILE:
702
    print qa_utils.FormatInfo("Test only supported for the file disk template")
703
    return
704

    
705
  cluster_name = qa_config.get("name")
706

    
707
  name = instance.name
708
  current = currentnode.primary
709
  other = othernode.primary
710

    
711
  # FIXME: the qa doesn't have a customizable file storage dir parameter. As
712
  # such for now we use the default.
713
  filestorage = pathutils.DEFAULT_FILE_STORAGE_DIR
714
  disk = os.path.join(filestorage, name)
715

    
716
  AssertCommand(["gnt-instance", "modify", "--new-primary=%s" % other, name],
717
                fail=True)
718
  AssertCommand(["gnt-instance", "shutdown", name])
719
  AssertCommand(["scp", "-oGlobalKnownHostsFile=%s" %
720
                 pathutils.SSH_KNOWN_HOSTS_FILE,
721
                 "-oCheckHostIp=no", "-oStrictHostKeyChecking=yes",
722
                 "-oHashKnownHosts=no", "-oHostKeyAlias=%s" % cluster_name,
723
                 "-r", disk, "%s:%s" % (other, filestorage)], node=current)
724
  AssertCommand(["gnt-instance", "modify", "--new-primary=%s" % other, name])
725
  AssertCommand(["gnt-instance", "startup", name])
726

    
727
  # and back
728
  AssertCommand(["gnt-instance", "shutdown", name])
729
  AssertCommand(["rm", "-rf", disk], node=other)
730
  AssertCommand(["gnt-instance", "modify", "--new-primary=%s" % current, name])
731
  AssertCommand(["gnt-instance", "startup", name])
732

    
733

    
734
@InstanceCheck(INST_DOWN, INST_DOWN, FIRST_ARG)
735
def TestInstanceStoppedModify(instance):
736
  """gnt-instance modify (stopped instance)"""
737
  name = instance.name
738

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

    
742
  # Mark instance as offline
743
  AssertCommand(["gnt-instance", "modify", "--offline", name])
744

    
745
  # When the instance is offline shutdown should only work with --force,
746
  # while start should never work
747
  AssertCommand(["gnt-instance", "shutdown", name], fail=True)
748
  AssertCommand(["gnt-instance", "shutdown", "--force", name])
749
  AssertCommand(["gnt-instance", "start", name], fail=True)
750
  AssertCommand(["gnt-instance", "start", "--force", name], fail=True)
751

    
752
  # Also do offline to offline
753
  AssertCommand(["gnt-instance", "modify", "--offline", name])
754

    
755
  # And online again
756
  AssertCommand(["gnt-instance", "modify", "--online", name])
757

    
758

    
759
@InstanceCheck(INST_DOWN, INST_DOWN, FIRST_ARG)
760
def TestInstanceConvertDiskToPlain(instance, inodes):
761
  """gnt-instance modify -t"""
762
  name = instance.name
763

    
764
  template = instance.disk_template
765
  if template != constants.DT_DRBD8:
766
    print qa_utils.FormatInfo("Unsupported template %s, skipping conversion"
767
                              " test" % template)
768
    return
769

    
770
  assert len(inodes) == 2
771
  AssertCommand(["gnt-instance", "modify", "-t", constants.DT_PLAIN, name])
772
  AssertCommand(["gnt-instance", "modify", "-t", constants.DT_DRBD8,
773
                 "-n", inodes[1].primary, name])
774

    
775

    
776
@InstanceCheck(INST_DOWN, INST_DOWN, FIRST_ARG)
777
def TestInstanceGrowDisk(instance):
778
  """gnt-instance grow-disk"""
779
  if qa_config.GetExclusiveStorage():
780
    print qa_utils.FormatInfo("Test not supported with exclusive_storage")
781
    return
782

    
783
  if instance.disk_template == constants.DT_DISKLESS:
784
    print qa_utils.FormatInfo("Test not supported for diskless instances")
785
    return
786

    
787
  name = instance.name
788
  disks = qa_config.GetDiskOptions()
789
  all_size = [d.get("size") for d in disks]
790
  all_grow = [d.get("growth") for d in disks]
791

    
792
  if not all_grow:
793
    # missing disk sizes but instance grow disk has been enabled,
794
    # let's set fixed/nomimal growth
795
    all_grow = ["128M" for _ in all_size]
796

    
797
  for idx, (size, grow) in enumerate(zip(all_size, all_grow)):
798
    # succeed in grow by amount
799
    AssertCommand(["gnt-instance", "grow-disk", name, str(idx), grow])
800
    # fail in grow to the old size
801
    AssertCommand(["gnt-instance", "grow-disk", "--absolute", name, str(idx),
802
                   size], fail=True)
803
    # succeed to grow to old size + 2 * growth
804
    int_size = utils.ParseUnit(size)
805
    int_grow = utils.ParseUnit(grow)
806
    AssertCommand(["gnt-instance", "grow-disk", "--absolute", name, str(idx),
807
                   str(int_size + 2 * int_grow)])
808

    
809

    
810
@InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
811
def TestInstanceDeviceNames(instance):
812
  if instance.disk_template == constants.DT_DISKLESS:
813
    print qa_utils.FormatInfo("Test not supported for diskless instances")
814
    return
815

    
816
  name = instance.name
817
  for dev_type in ["disk", "net"]:
818
    if dev_type == "disk":
819
      options = ",size=512M"
820
    else:
821
      options = ""
822
    # succeed in adding a device named 'test_device'
823
    AssertCommand(["gnt-instance", "modify",
824
                   "--%s=-1:add,name=test_device%s" % (dev_type, options),
825
                   name])
826
    # succeed in removing the 'test_device'
827
    AssertCommand(["gnt-instance", "modify",
828
                   "--%s=test_device:remove" % dev_type,
829
                   name])
830
    # fail to add two devices with the same name
831
    AssertCommand(["gnt-instance", "modify",
832
                   "--%s=-1:add,name=test_device%s" % (dev_type, options),
833
                   "--%s=-1:add,name=test_device%s" % (dev_type, options),
834
                   name], fail=True)
835
    # fail to add a device with invalid name
836
    AssertCommand(["gnt-instance", "modify",
837
                   "--%s=-1:add,name=2%s" % (dev_type, options),
838
                   name], fail=True)
839
  # Rename disks
840
  disks = qa_config.GetDiskOptions()
841
  disk_names = [d.get("name") for d in disks]
842
  for idx, disk_name in enumerate(disk_names):
843
    # Refer to disk by idx
844
    AssertCommand(["gnt-instance", "modify",
845
                   "--disk=%s:modify,name=renamed" % idx,
846
                   name])
847
    # Refer to by name and rename to original name
848
    AssertCommand(["gnt-instance", "modify",
849
                   "--disk=renamed:modify,name=%s" % disk_name,
850
                   name])
851
  if len(disks) >= 2:
852
    # fail in renaming to disks to the same name
853
    AssertCommand(["gnt-instance", "modify",
854
                   "--disk=0:modify,name=same_name",
855
                   "--disk=1:modify,name=same_name",
856
                   name], fail=True)
857

    
858

    
859
def TestInstanceList():
860
  """gnt-instance list"""
861
  qa_utils.GenericQueryTest("gnt-instance", query.INSTANCE_FIELDS.keys())
862

    
863

    
864
def TestInstanceListFields():
865
  """gnt-instance list-fields"""
866
  qa_utils.GenericQueryFieldsTest("gnt-instance", query.INSTANCE_FIELDS.keys())
867

    
868

    
869
@InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
870
def TestInstanceConsole(instance):
871
  """gnt-instance console"""
872
  AssertCommand(["gnt-instance", "console", "--show-cmd", instance.name])
873

    
874

    
875
@InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
876
def TestReplaceDisks(instance, curr_nodes, other_nodes):
877
  """gnt-instance replace-disks"""
878
  def buildcmd(args):
879
    cmd = ["gnt-instance", "replace-disks"]
880
    cmd.extend(args)
881
    cmd.append(instance.name)
882
    return cmd
883

    
884
  if not IsDiskReplacingSupported(instance):
885
    print qa_utils.FormatInfo("Instance doesn't support disk replacing,"
886
                              " skipping test")
887
    return
888

    
889
  # Currently all supported templates have one primary and one secondary node
890
  assert len(curr_nodes) == 2
891
  snode = curr_nodes[1]
892
  assert len(other_nodes) == 1
893
  othernode = other_nodes[0]
894

    
895
  options = qa_config.get("options", {})
896
  use_ialloc = options.get("use-iallocators", True)
897
  for data in [
898
    ["-p"],
899
    ["-s"],
900
    # A placeholder; the actual command choice depends on use_ialloc
901
    None,
902
    # Restore the original secondary
903
    ["--new-secondary=%s" % snode.primary],
904
    ]:
905
    if data is None:
906
      if use_ialloc:
907
        data = ["-I", constants.DEFAULT_IALLOCATOR_SHORTCUT]
908
      else:
909
        data = ["--new-secondary=%s" % othernode.primary]
910
    AssertCommand(buildcmd(data))
911

    
912
  AssertCommand(buildcmd(["-a"]))
913
  AssertCommand(["gnt-instance", "stop", instance.name])
914
  AssertCommand(buildcmd(["-a"]), fail=True)
915
  AssertCommand(["gnt-instance", "activate-disks", instance.name])
916
  AssertCommand(["gnt-instance", "activate-disks", "--wait-for-sync",
917
                 instance.name])
918
  AssertCommand(buildcmd(["-a"]))
919
  AssertCommand(["gnt-instance", "start", instance.name])
920

    
921

    
922
def _AssertRecreateDisks(cmdargs, instance, fail=False, check=True,
923
                         destroy=True):
924
  """Execute gnt-instance recreate-disks and check the result
925

926
  @param cmdargs: Arguments (instance name excluded)
927
  @param instance: Instance to operate on
928
  @param fail: True if the command is expected to fail
929
  @param check: If True and fail is False, check that the disks work
930
  @prama destroy: If True, destroy the old disks first
931

932
  """
933
  if destroy:
934
    _DestroyInstanceDisks(instance)
935
  AssertCommand((["gnt-instance", "recreate-disks"] + cmdargs +
936
                 [instance.name]), fail)
937
  if not fail and check:
938
    # Quick check that the disks are there
939
    AssertCommand(["gnt-instance", "activate-disks", instance.name])
940
    AssertCommand(["gnt-instance", "activate-disks", "--wait-for-sync",
941
                   instance.name])
942
    AssertCommand(["gnt-instance", "deactivate-disks", instance.name])
943

    
944

    
945
@InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
946
def TestRecreateDisks(instance, inodes, othernodes):
947
  """gnt-instance recreate-disks
948

949
  @param instance: Instance to work on
950
  @param inodes: List of the current nodes of the instance
951
  @param othernodes: list/tuple of nodes where to temporarily recreate disks
952

953
  """
954
  options = qa_config.get("options", {})
955
  use_ialloc = options.get("use-iallocators", True)
956
  other_seq = ":".join([n.primary for n in othernodes])
957
  orig_seq = ":".join([n.primary for n in inodes])
958
  # These fail because the instance is running
959
  _AssertRecreateDisks(["-n", other_seq], instance, fail=True, destroy=False)
960
  if use_ialloc:
961
    _AssertRecreateDisks(["-I", "hail"], instance, fail=True, destroy=False)
962
  else:
963
    _AssertRecreateDisks(["-n", other_seq], instance, fail=True, destroy=False)
964
  AssertCommand(["gnt-instance", "stop", instance.name])
965
  # Disks exist: this should fail
966
  _AssertRecreateDisks([], instance, fail=True, destroy=False)
967
  # Recreate disks in place
968
  _AssertRecreateDisks([], instance)
969
  # Move disks away
970
  if use_ialloc:
971
    _AssertRecreateDisks(["-I", "hail"], instance)
972
    # Move disks somewhere else
973
    _AssertRecreateDisks(["-I", constants.DEFAULT_IALLOCATOR_SHORTCUT],
974
                         instance)
975
  else:
976
    _AssertRecreateDisks(["-n", other_seq], instance)
977
  # Move disks back
978
  _AssertRecreateDisks(["-n", orig_seq], instance, check=False)
979
  # This and InstanceCheck decoration check that the disks are working
980
  AssertCommand(["gnt-instance", "reinstall", "-f", instance.name])
981
  AssertCommand(["gnt-instance", "start", instance.name])
982

    
983

    
984
@InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
985
def TestInstanceExport(instance, node):
986
  """gnt-backup export -n ..."""
987
  name = instance.name
988
  AssertCommand(["gnt-backup", "export", "-n", node.primary, name])
989
  return qa_utils.ResolveInstanceName(name)
990

    
991

    
992
@InstanceCheck(None, INST_DOWN, FIRST_ARG)
993
def TestInstanceExportWithRemove(instance, node):
994
  """gnt-backup export --remove-instance"""
995
  AssertCommand(["gnt-backup", "export", "-n", node.primary,
996
                 "--remove-instance", instance.name])
997

    
998

    
999
@InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
1000
def TestInstanceExportNoTarget(instance):
1001
  """gnt-backup export (without target node, should fail)"""
1002
  AssertCommand(["gnt-backup", "export", instance.name], fail=True)
1003

    
1004

    
1005
@InstanceCheck(None, INST_DOWN, FIRST_ARG)
1006
def TestInstanceImport(newinst, node, expnode, name):
1007
  """gnt-backup import"""
1008
  templ = constants.DT_PLAIN
1009
  cmd = (["gnt-backup", "import",
1010
          "--disk-template=%s" % templ,
1011
          "--no-ip-check",
1012
          "--src-node=%s" % expnode.primary,
1013
          "--src-dir=%s/%s" % (pathutils.EXPORT_DIR, name),
1014
          "--node=%s" % node.primary] +
1015
         _GetGenericAddParameters(newinst, templ,
1016
                                  force_mac=constants.VALUE_GENERATE))
1017
  cmd.append(newinst.name)
1018
  AssertCommand(cmd)
1019
  newinst.SetDiskTemplate(templ)
1020

    
1021

    
1022
def TestBackupList(expnode):
1023
  """gnt-backup list"""
1024
  AssertCommand(["gnt-backup", "list", "--node=%s" % expnode.primary])
1025

    
1026
  qa_utils.GenericQueryTest("gnt-backup", query.EXPORT_FIELDS.keys(),
1027
                            namefield=None, test_unknown=False)
1028

    
1029

    
1030
def TestBackupListFields():
1031
  """gnt-backup list-fields"""
1032
  qa_utils.GenericQueryFieldsTest("gnt-backup", query.EXPORT_FIELDS.keys())
1033

    
1034

    
1035
def TestRemoveInstanceOfflineNode(instance, snode, set_offline, set_online):
1036
  """gnt-instance remove with an off-line node
1037

1038
  @param instance: instance
1039
  @param snode: secondary node, to be set offline
1040
  @param set_offline: function to call to set the node off-line
1041
  @param set_online: function to call to set the node on-line
1042

1043
  """
1044
  info = _GetInstanceInfo(instance.name)
1045
  set_offline(snode)
1046
  try:
1047
    TestInstanceRemove(instance)
1048
  finally:
1049
    set_online(snode)
1050

    
1051
  # Clean up the disks on the offline node, if necessary
1052
  if instance.disk_template not in constants.DTS_EXT_MIRROR:
1053
    # FIXME: abstract the cleanup inside the disks
1054
    if info["storage-type"] == constants.ST_LVM_VG:
1055
      for minor in info["drbd-minors"][snode.primary]:
1056
        AssertCommand(["drbdsetup", str(minor), "down"], node=snode)
1057
      AssertCommand(["lvremove", "-f"] + info["volumes"], node=snode)
1058
    elif info["storage-type"] == constants.ST_FILE:
1059
      filestorage = pathutils.DEFAULT_FILE_STORAGE_DIR
1060
      disk = os.path.join(filestorage, instance.name)
1061
      AssertCommand(["rm", "-rf", disk], node=snode)
1062

    
1063

    
1064
def TestInstanceCreationRestrictedByDiskTemplates():
1065
  """Test adding instances for disbled disk templates."""
1066
  enabled_disk_templates = qa_config.GetEnabledDiskTemplates()
1067
  nodes = qa_config.AcquireManyNodes(2)
1068

    
1069
  # Setup the cluster with the enabled_disk_templates
1070
  AssertCommand(
1071
    ["gnt-cluster", "modify",
1072
     "--enabled-disk-template=%s" %
1073
       ",".join(enabled_disk_templates)],
1074
    fail=False)
1075

    
1076
  # Test instance creation for enabled disk templates
1077
  for disk_template in enabled_disk_templates:
1078
    instance = CreateInstanceByDiskTemplate(nodes, disk_template, False)
1079
    TestInstanceRemove(instance)
1080

    
1081
  # Test that instance creation fails for disabled disk templates
1082
  disabled_disk_templates = list(constants.DISK_TEMPLATES
1083
                                 - set(enabled_disk_templates))
1084
  for disk_template in disabled_disk_templates:
1085
    instance = CreateInstanceByDiskTemplate(nodes, disk_template, True)
1086

    
1087
  # Test instance creation for after disabling enabled disk templates
1088
  if (len(enabled_disk_templates) > 1):
1089
    # Partition the disk templates, enable them separately and check if the
1090
    # disabled ones cannot be used by instances.
1091
    middle = len(enabled_disk_templates) / 2
1092
    templates1 = enabled_disk_templates[:middle]
1093
    templates2 = enabled_disk_templates[middle:]
1094

    
1095
    for (enabled, disabled) in [(templates1, templates2),
1096
                                (templates2, templates1)]:
1097
      AssertCommand(["gnt-cluster", "modify",
1098
                     "--enabled-disk-template=%s" %
1099
                       ",".join(enabled)],
1100
                    fail=False)
1101
      for disk_template in disabled:
1102
        CreateInstanceByDiskTemplate(nodes, disk_template, True)
1103
  elif (len(enabled_disk_templates) == 1):
1104
    # If only one disk template is enabled in the QA config, we have to enable
1105
    # some of the disabled disk templates in order to test if the disabling the
1106
    # only enabled disk template prohibits creating instances of that template.
1107
    AssertCommand(["gnt-cluster", "modify",
1108
                   "--enabled-disk-template=%s" %
1109
                     ",".join(disabled_disk_templates)],
1110
                  fail=False)
1111
    CreateInstanceByDiskTemplate(nodes, enabled_disk_templates[0], True)
1112
  else:
1113
    raise qa_error.Error("Please enable at least one disk template"
1114
                         " in your QA setup.")
1115

    
1116
  # Restore initially enabled disk templates
1117
  AssertCommand(["gnt-cluster", "modify",
1118
                 "--enabled-disk-template=%s" %
1119
                   ",".join(enabled_disk_templates)],
1120
                 fail=False)