Statistics
| Branch: | Tag: | Revision:

root / qa / qa_instance.py @ 090128b6

History | View | Annotate | Download (38.8 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
      params.extend(["--disk", "%s:size=%s,name=%s" % (idx, size, name)])
59

    
60
  # Set static MAC address if configured
61
  if force_mac:
62
    nic0_mac = force_mac
63
  else:
64
    nic0_mac = inst.GetNicMacAddr(0, None)
65

    
66
  if nic0_mac:
67
    params.extend(["--net", "0:mac=%s" % nic0_mac])
68

    
69
  return params
70

    
71

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

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

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

    
94
    AssertCommand(cmd, fail=fail)
95

    
96
    if not fail:
97
      _CheckSsconfInstanceList(instance.name)
98
      instance.SetDiskTemplate(disk_template)
99

    
100
      return instance
101
  except:
102
    instance.Release()
103
    raise
104

    
105
  # Handle the case where creation is expected to fail
106
  assert fail
107
  instance.Release()
108
  return None
109

    
110

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

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

123
  """
124
  assert len(nodes) > 0
125
  return _CreateInstanceByDiskTemplateRaw(nodes[0].primary, disk_template,
126
                                          fail=fail)
127

    
128

    
129
def _CreateInstanceDrbd8(nodes, fail=False):
130
  """Creates an instance using disk template 'drbd' on the given nodes.
131

132
  @type nodes: list of nodes
133
  @param nodes: nodes to be used by the instance
134
  @return: the created instance
135

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

    
142

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

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

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

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

    
175

    
176
def _GetInstanceInfo(instance):
177
  """Return information about the actual state of an instance.
178

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

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

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

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

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

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

    
247

    
248
def _DestroyInstanceDisks(instance):
249
  """Remove all the backend disks of an instance.
250

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

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

    
273

    
274
def _GetInstanceField(instance, field):
275
  """Get the value of a field of an instance.
276

277
  @type instance: string
278
  @param instance: Instance name
279
  @type field: string
280
  @param field: Name of the field
281
  @rtype: string
282

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

    
289

    
290
def _GetBoolInstanceField(instance, field):
291
  """Get the Boolean value of a field of an instance.
292

293
  @type instance: string
294
  @param instance: Instance name
295
  @type field: string
296
  @param field: Name of the field
297
  @rtype: bool
298

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

    
309

    
310
def _GetNumInstanceField(instance, field):
311
  """Get a numeric value of a field of an instance.
312

313
  @type instance: string
314
  @param instance: Instance name
315
  @type field: string
316
  @param field: Name of the field
317
  @rtype: int or float
318

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

    
331

    
332
def GetInstanceSpec(instance, spec):
333
  """Return the current spec for the given parameter.
334

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

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

    
362

    
363
def IsFailoverSupported(instance):
364
  return instance.disk_template in constants.DTS_MIRRORED
365

    
366

    
367
def IsMigrationSupported(instance):
368
  return instance.disk_template in constants.DTS_MIRRORED
369

    
370

    
371
def IsDiskReplacingSupported(instance):
372
  return instance.disk_template == constants.DT_DRBD8
373

    
374

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

    
384

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

    
391

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

    
399

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

    
407

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

    
413

    
414
@InstanceCheck(INST_DOWN, INST_UP, FIRST_ARG)
415
def TestInstanceStartup(instance):
416
  """gnt-instance startup"""
417
  AssertCommand(["gnt-instance", "startup", instance.name])
418

    
419

    
420
@InstanceCheck(INST_UP, INST_DOWN, FIRST_ARG)
421
def TestInstanceShutdown(instance):
422
  """gnt-instance shutdown"""
423
  AssertCommand(["gnt-instance", "shutdown", instance.name])
424

    
425

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

    
435
  AssertCommand(["gnt-instance", "shutdown", name])
436
  qa_utils.RunInstanceCheck(instance, False)
437
  AssertCommand(["gnt-instance", "reboot", name])
438

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

    
445

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

    
453
  AssertCommand(["gnt-instance", "reinstall", "-f", instance.name])
454

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

    
461

    
462
def _ReadSsconfInstanceList():
463
  """Reads ssconf_instance_list from the master node.
464

465
  """
466
  master = qa_config.GetMasterNode()
467

    
468
  ssconf_path = utils.PathJoin(pathutils.DATA_DIR,
469
                               "ssconf_%s" % constants.SS_INSTANCE_LIST)
470

    
471
  cmd = ["cat", qa_utils.MakeNodePath(master, ssconf_path)]
472

    
473
  return qa_utils.GetCommandOutput(master.primary,
474
                                   utils.ShellQuoteArgs(cmd)).splitlines()
475

    
476

    
477
def _CheckSsconfInstanceList(instance):
478
  """Checks if a certain instance is in the ssconf instance list.
479

480
  @type instance: string
481
  @param instance: Instance name
482

483
  """
484
  AssertIn(qa_utils.ResolveInstanceName(instance),
485
           _ReadSsconfInstanceList())
486

    
487

    
488
@InstanceCheck(INST_DOWN, INST_DOWN, FIRST_ARG)
489
def TestInstanceRenameAndBack(rename_source, rename_target):
490
  """gnt-instance rename
491

492
  This must leave the instance with the original name, not the target
493
  name.
494

495
  """
496
  _CheckSsconfInstanceList(rename_source)
497

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

    
507
  info = _GetInstanceInfo(rename_source)
508

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

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

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

    
538
  # and back
539
  AssertCommand(["gnt-instance", "rename", rename_target, rename_source])
540
  _CheckSsconfInstanceList(rename_source)
541
  qa_utils.RunInstanceCheck(rename_target, False)
542

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

    
549

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

    
558
  cmd = ["gnt-instance", "failover", "--force", instance.name]
559

    
560
  # failover ...
561
  AssertCommand(cmd)
562
  qa_utils.RunInstanceCheck(instance, True)
563

    
564
  # ... and back
565
  AssertCommand(cmd)
566

    
567

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

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

    
581
  # migrate ...
582
  AssertCommand(cmd)
583
  # TODO: Verify the choice between failover and migration
584
  qa_utils.RunInstanceCheck(instance, True)
585

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

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

    
609
  AssertCommand(["gnt-instance", "modify", "-B",
610
                 ("%s=%s" %
611
                  (constants.BE_ALWAYS_FAILOVER, constants.VALUE_TRUE)),
612
                 instance.name])
613

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

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

    
624
  AssertCommand(cmd)
625
  qa_utils.RunInstanceCheck(instance, True)
626

    
627

    
628
def TestInstanceInfo(instance):
629
  """gnt-instance info"""
630
  AssertCommand(["gnt-instance", "info", instance.name])
631

    
632

    
633
@InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
634
def TestInstanceModify(instance):
635
  """gnt-instance modify"""
636
  default_hv = qa_config.GetDefaultHypervisor()
637

    
638
  # Assume /sbin/init exists on all systems
639
  test_kernel = "/sbin/init"
640
  test_initrd = test_kernel
641

    
642
  orig_maxmem = qa_config.get(constants.BE_MAXMEM)
643
  orig_minmem = qa_config.get(constants.BE_MINMEM)
644
  #orig_bridge = qa_config.get("bridge", "xen-br0")
645

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

    
657
    ["-H", "%s=%s" % (constants.HV_KERNEL_PATH, test_kernel)],
658
    ["-H", "%s=%s" % (constants.HV_KERNEL_PATH, constants.VALUE_DEFAULT)],
659

    
660
    # TODO: bridge tests
661
    #["--bridge", "xen-br1"],
662
    #["--bridge", orig_bridge],
663
    ]
664

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

    
677
  for alist in args:
678
    AssertCommand(["gnt-instance", "modify"] + alist + [instance.name])
679

    
680
  # check no-modify
681
  AssertCommand(["gnt-instance", "modify", instance.name], fail=True)
682

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

    
687
  # ...while making it online is ok, and should work
688
  AssertCommand(["gnt-instance", "modify", "--online", instance.name])
689

    
690

    
691
@InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
692
def TestInstanceModifyPrimaryAndBack(instance, currentnode, othernode):
693
  """gnt-instance modify --new-primary
694

695
  This will leave the instance on its original primary node, not other node.
696

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

    
702
  cluster_name = qa_config.get("name")
703

    
704
  name = instance.name
705
  current = currentnode.primary
706
  other = othernode.primary
707

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

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

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

    
730

    
731
@InstanceCheck(INST_DOWN, INST_DOWN, FIRST_ARG)
732
def TestInstanceStoppedModify(instance):
733
  """gnt-instance modify (stopped instance)"""
734
  name = instance.name
735

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

    
739
  # Mark instance as offline
740
  AssertCommand(["gnt-instance", "modify", "--offline", name])
741

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

    
749
  # Also do offline to offline
750
  AssertCommand(["gnt-instance", "modify", "--offline", name])
751

    
752
  # And online again
753
  AssertCommand(["gnt-instance", "modify", "--online", name])
754

    
755

    
756
@InstanceCheck(INST_DOWN, INST_DOWN, FIRST_ARG)
757
def TestInstanceConvertDiskToPlain(instance, inodes):
758
  """gnt-instance modify -t"""
759
  name = instance.name
760

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

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

    
772

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

    
780
  if instance.disk_template == constants.DT_DISKLESS:
781
    print qa_utils.FormatInfo("Test not supported for diskless instances")
782
    return
783

    
784
  name = instance.name
785
  disks = qa_config.GetDiskOptions()
786
  all_size = [d.get("size") for d in disks]
787
  all_grow = [d.get("growth") for d in disks]
788

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

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

    
806

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

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

    
855

    
856
def TestInstanceList():
857
  """gnt-instance list"""
858
  qa_utils.GenericQueryTest("gnt-instance", query.INSTANCE_FIELDS.keys())
859

    
860

    
861
def TestInstanceListFields():
862
  """gnt-instance list-fields"""
863
  qa_utils.GenericQueryFieldsTest("gnt-instance", query.INSTANCE_FIELDS.keys())
864

    
865

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

    
871

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

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

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

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

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

    
918

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

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

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

    
941

    
942
@InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
943
def TestRecreateDisks(instance, inodes, othernodes):
944
  """gnt-instance recreate-disks
945

946
  @param instance: Instance to work on
947
  @param inodes: List of the current nodes of the instance
948
  @param othernodes: list/tuple of nodes where to temporarily recreate disks
949

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

    
980

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

    
988

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

    
995

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

    
1001

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

    
1018

    
1019
def TestBackupList(expnode):
1020
  """gnt-backup list"""
1021
  AssertCommand(["gnt-backup", "list", "--node=%s" % expnode.primary])
1022

    
1023
  qa_utils.GenericQueryTest("gnt-backup", query.EXPORT_FIELDS.keys(),
1024
                            namefield=None, test_unknown=False)
1025

    
1026

    
1027
def TestBackupListFields():
1028
  """gnt-backup list-fields"""
1029
  qa_utils.GenericQueryFieldsTest("gnt-backup", query.EXPORT_FIELDS.keys())
1030

    
1031

    
1032
def TestRemoveInstanceOfflineNode(instance, snode, set_offline, set_online):
1033
  """gnt-instance remove with an off-line node
1034

1035
  @param instance: instance
1036
  @param snode: secondary node, to be set offline
1037
  @param set_offline: function to call to set the node off-line
1038
  @param set_online: function to call to set the node on-line
1039

1040
  """
1041
  info = _GetInstanceInfo(instance.name)
1042
  set_offline(snode)
1043
  try:
1044
    TestInstanceRemove(instance)
1045
  finally:
1046
    set_online(snode)
1047

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

    
1060

    
1061
def TestInstanceCreationRestrictedByDiskTemplates():
1062
  """Test adding instances for disbled disk templates."""
1063
  enabled_disk_templates = qa_config.GetEnabledDiskTemplates()
1064
  nodes = qa_config.AcquireManyNodes(2)
1065

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

    
1073
  # Test instance creation for enabled disk templates
1074
  for disk_template in enabled_disk_templates:
1075
    instance = CreateInstanceByDiskTemplate(nodes, disk_template, False)
1076
    TestInstanceRemove(instance)
1077

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

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

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

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