Statistics
| Branch: | Tag: | Revision:

root / qa / qa_instance.py @ a07ae57f

History | View | Annotate | Download (50.1 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
import time
29

    
30
from ganeti import utils
31
from ganeti import constants
32
from ganeti import pathutils
33
from ganeti import query
34
from ganeti.netutils import IP4Address
35

    
36
import qa_config
37
import qa_daemon
38
import qa_utils
39
import qa_error
40

    
41
from qa_utils import AssertCommand, AssertEqual, AssertIn
42
from qa_utils import InstanceCheck, INST_DOWN, INST_UP, FIRST_ARG, RETURN_VALUE
43
from qa_instance_utils import CheckSsconfInstanceList, \
44
                              CreateInstanceDrbd8, \
45
                              CreateInstanceByDiskTemplate, \
46
                              CreateInstanceByDiskTemplateOneNode, \
47
                              GetGenericAddParameters
48

    
49

    
50
def _GetDiskStatePath(disk):
51
  return "/sys/block/%s/device/state" % disk
52

    
53

    
54
def GetInstanceInfo(instance):
55
  """Return information about the actual state of an instance.
56

57
  @type instance: string
58
  @param instance: the instance name
59
  @return: a dictionary with the following keys:
60
      - "nodes": instance nodes, a list of strings
61
      - "volumes": instance volume IDs, a list of strings
62
      - "drbd-minors": DRBD minors used by the instance, a dictionary where
63
        keys are nodes, and values are lists of integers (or an empty
64
        dictionary for non-DRBD instances)
65
      - "disk-template": instance disk template
66
      - "storage-type": storage type associated with the instance disk template
67

68
  """
69
  node_elem = r"([^,()]+)(?:\s+\([^)]+\))?"
70
  # re_nodelist matches a list of nodes returned by gnt-instance info, e.g.:
71
  #  node1.fqdn
72
  #  node2.fqdn,node3.fqdn
73
  #  node4.fqdn (group mygroup, group UUID 01234567-abcd-0123-4567-0123456789ab)
74
  # FIXME This works with no more than 2 secondaries
75
  re_nodelist = re.compile(node_elem + "(?:," + node_elem + ")?$")
76

    
77
  info = qa_utils.GetObjectInfo(["gnt-instance", "info", instance])[0]
78
  nodes = []
79
  for nodeinfo in info["Nodes"]:
80
    if "primary" in nodeinfo:
81
      nodes.append(nodeinfo["primary"])
82
    elif "secondaries" in nodeinfo:
83
      nodestr = nodeinfo["secondaries"]
84
      if nodestr:
85
        m = re_nodelist.match(nodestr)
86
        if m:
87
          nodes.extend(filter(None, m.groups()))
88
        else:
89
          nodes.append(nodestr)
90

    
91
  disk_template = info["Disk template"]
92
  if not disk_template:
93
    raise qa_error.Error("Can't get instance disk template")
94
  storage_type = constants.MAP_DISK_TEMPLATE_STORAGE_TYPE[disk_template]
95

    
96
  re_drbdnode = re.compile(r"^([^\s,]+),\s+minor=([0-9]+)$")
97
  vols = []
98
  drbd_min = {}
99
  for (count, diskinfo) in enumerate(info["Disks"]):
100
    (dtype, _) = diskinfo["disk/%s" % count].split(",", 1)
101
    if dtype == constants.DT_DRBD8:
102
      for child in diskinfo["child devices"]:
103
        vols.append(child["logical_id"])
104
      for key in ["nodeA", "nodeB"]:
105
        m = re_drbdnode.match(diskinfo[key])
106
        if not m:
107
          raise qa_error.Error("Cannot parse DRBD info: %s" % diskinfo[key])
108
        node = m.group(1)
109
        minor = int(m.group(2))
110
        minorlist = drbd_min.setdefault(node, [])
111
        minorlist.append(minor)
112
    elif dtype == constants.DT_PLAIN:
113
      vols.append(diskinfo["logical_id"])
114

    
115
  assert nodes
116
  assert len(nodes) < 2 or vols
117
  return {
118
    "nodes": nodes,
119
    "volumes": vols,
120
    "drbd-minors": drbd_min,
121
    "disk-template": disk_template,
122
    "storage-type": storage_type,
123
    }
124

    
125

    
126
def _DestroyInstanceDisks(instance):
127
  """Remove all the backend disks of an instance.
128

129
  This is used to simulate HW errors (dead nodes, broken disks...); the
130
  configuration of the instance is not affected.
131
  @type instance: dictionary
132
  @param instance: the instance
133

134
  """
135
  info = GetInstanceInfo(instance.name)
136
  # FIXME: destruction/removal should be part of the disk class
137
  if info["storage-type"] == constants.ST_LVM_VG:
138
    vols = info["volumes"]
139
    for node in info["nodes"]:
140
      AssertCommand(["lvremove", "-f"] + vols, node=node)
141
  elif info["storage-type"] in (constants.ST_FILE, constants.ST_SHARED_FILE):
142
    # Note that this works for both file and sharedfile, and this is intended.
143
    storage_dir = qa_config.get("file-storage-dir",
144
                                pathutils.DEFAULT_FILE_STORAGE_DIR)
145
    idir = os.path.join(storage_dir, instance.name)
146
    for node in info["nodes"]:
147
      AssertCommand(["rm", "-rf", idir], node=node)
148
  elif info["storage-type"] == constants.ST_DISKLESS:
149
    pass
150

    
151

    
152
def _GetInstanceField(instance, field):
153
  """Get the value of a field of an instance.
154

155
  @type instance: string
156
  @param instance: Instance name
157
  @type field: string
158
  @param field: Name of the field
159
  @rtype: string
160

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

    
167

    
168
def _GetBoolInstanceField(instance, field):
169
  """Get the Boolean value of a field of an instance.
170

171
  @type instance: string
172
  @param instance: Instance name
173
  @type field: string
174
  @param field: Name of the field
175
  @rtype: bool
176

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

    
187

    
188
def _GetNumInstanceField(instance, field):
189
  """Get a numeric value of a field of an instance.
190

191
  @type instance: string
192
  @param instance: Instance name
193
  @type field: string
194
  @param field: Name of the field
195
  @rtype: int or float
196

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

    
209

    
210
def GetInstanceSpec(instance, spec):
211
  """Return the current spec for the given parameter.
212

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

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

    
240

    
241
def IsFailoverSupported(instance):
242
  return instance.disk_template in constants.DTS_MIRRORED
243

    
244

    
245
def IsMigrationSupported(instance):
246
  return instance.disk_template in constants.DTS_MIRRORED
247

    
248

    
249
def IsDiskReplacingSupported(instance):
250
  return instance.disk_template == constants.DT_DRBD8
251

    
252

    
253
def IsDiskSupported(instance):
254
  return instance.disk_template != constants.DT_DISKLESS
255

    
256

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

    
266

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

    
273

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

    
281

    
282
@InstanceCheck(None, INST_UP, RETURN_VALUE)
283
def TestInstanceAddSharedFile(nodes):
284
  """gnt-instance add -t sharedfile"""
285
  assert len(nodes) == 1
286
  if constants.DT_SHARED_FILE in qa_config.GetEnabledDiskTemplates():
287
    return CreateInstanceByDiskTemplateOneNode(nodes, constants.DT_SHARED_FILE)
288

    
289

    
290
@InstanceCheck(None, INST_UP, RETURN_VALUE)
291
def TestInstanceAddDiskless(nodes):
292
  """gnt-instance add -t diskless"""
293
  assert len(nodes) == 1
294
  if constants.DT_DISKLESS in qa_config.GetEnabledDiskTemplates():
295
    return CreateInstanceByDiskTemplateOneNode(nodes, constants.DT_DISKLESS)
296

    
297

    
298
@InstanceCheck(None, INST_DOWN, FIRST_ARG)
299
def TestInstanceRemove(instance):
300
  """gnt-instance remove"""
301
  AssertCommand(["gnt-instance", "remove", "-f", instance.name])
302

    
303

    
304
@InstanceCheck(INST_DOWN, INST_UP, FIRST_ARG)
305
def TestInstanceStartup(instance):
306
  """gnt-instance startup"""
307
  AssertCommand(["gnt-instance", "startup", instance.name])
308

    
309

    
310
@InstanceCheck(INST_UP, INST_DOWN, FIRST_ARG)
311
def TestInstanceShutdown(instance):
312
  """gnt-instance shutdown"""
313
  AssertCommand(["gnt-instance", "shutdown", instance.name])
314

    
315

    
316
@InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
317
def TestInstanceReboot(instance):
318
  """gnt-instance reboot"""
319
  options = qa_config.get("options", {})
320
  reboot_types = options.get("reboot-types", constants.REBOOT_TYPES)
321
  name = instance.name
322
  for rtype in reboot_types:
323
    AssertCommand(["gnt-instance", "reboot", "--type=%s" % rtype, name])
324

    
325
  AssertCommand(["gnt-instance", "shutdown", name])
326
  qa_utils.RunInstanceCheck(instance, False)
327
  AssertCommand(["gnt-instance", "reboot", name])
328

    
329
  master = qa_config.GetMasterNode()
330
  cmd = ["gnt-instance", "list", "--no-headers", "-o", "status", name]
331
  result_output = qa_utils.GetCommandOutput(master.primary,
332
                                            utils.ShellQuoteArgs(cmd))
333
  AssertEqual(result_output.strip(), constants.INSTST_RUNNING)
334

    
335

    
336
@InstanceCheck(INST_DOWN, INST_DOWN, FIRST_ARG)
337
def TestInstanceReinstall(instance):
338
  """gnt-instance reinstall"""
339
  if instance.disk_template == constants.DT_DISKLESS:
340
    print qa_utils.FormatInfo("Test not supported for diskless instances")
341
    return
342

    
343
  master = qa_config.GetMasterNode()
344

    
345
  AssertCommand(["gnt-backup", "export", "-n", master.primary, instance.name])
346

    
347
  instance_info = GetInstanceInfo(instance.name)
348
  disk0 = None
349

    
350
  for volume in instance_info["volumes"]:
351
    if "disk0" in volume:
352
      i = volume.find("/")
353
      if i >= 0:
354
        disk0_id = volume[(i + 1):]
355
      else:
356
        disk0_id = volume
357

    
358
      disk0_path = "/srv/ganeti/export/%s/%s.snap" % (instance.name, disk0_id)
359

    
360
      try:
361
        AssertCommand(["stat", disk0_path])
362
        disk0 = disk0_path
363
        break
364
      except qa_error.Error:
365
        pass
366

    
367
  if disk0 is None:
368
    raise qa_error.Error("Could not determine exported disk for instance '%s'" %
369
                         instance.name)
370

    
371
  image = qa_utils.BackupFile(master.primary, disk0)
372

    
373
  AssertCommand(["gnt-instance", "reinstall",
374
                 "--os-parameters", "os-image=" + image,
375
                 "-f", instance.name])
376

    
377
  try:
378
    port = 8000
379

    
380
    while True:
381
      cmd = "( cd /srv/ganeti/export; python -m SimpleHTTPServer %d )" % port
382
      ssh_cmd = qa_utils.GetSSHCommand(master.primary, cmd)
383

    
384
      try:
385
        server_process = qa_utils.StartLocalCommand(ssh_cmd)
386
        break
387
      except OSError:
388
        if port < 9000:
389
          port += 1
390
        else:
391
          raise
392

    
393
    url = "http://localhost:%d/%s" % (port, os.path.basename(image))
394
    AssertCommand(["gnt-instance", "reinstall",
395
                   "--os-parameters", "os-image=" + url,
396
                   "-f", instance.name])
397

    
398
    AssertCommand(["rm", "-f", image])
399
  finally:
400
    server_process.terminate()
401

    
402
  AssertCommand(["gnt-instance", "reinstall",
403
                 "--os-parameters", "os-image=NonExistantOsForQa",
404
                 "-f", instance.name], fail=True)
405

    
406
  AssertCommand(["gnt-instance", "reinstall",
407
                 "--os-parameters", "os-image=http://NonExistantOsForQa",
408
                 "-f", instance.name], fail=True)
409

    
410
  AssertCommand(["gnt-instance", "reinstall", "-f", instance.name])
411

    
412
  # Test with non-existant OS definition
413
  AssertCommand(["gnt-instance", "reinstall", "-f",
414
                 "--os-type=NonExistantOsForQa",
415
                 instance.name],
416
                fail=True)
417

    
418

    
419
@InstanceCheck(INST_DOWN, INST_DOWN, FIRST_ARG)
420
def TestInstanceRenameAndBack(rename_source, rename_target):
421
  """gnt-instance rename
422

423
  This must leave the instance with the original name, not the target
424
  name.
425

426
  """
427
  CheckSsconfInstanceList(rename_source)
428

    
429
  # first do a rename to a different actual name, expecting it to fail
430
  qa_utils.AddToEtcHosts(["meeeeh-not-exists", rename_target])
431
  try:
432
    AssertCommand(["gnt-instance", "rename", rename_source, rename_target],
433
                  fail=True)
434
    CheckSsconfInstanceList(rename_source)
435
  finally:
436
    qa_utils.RemoveFromEtcHosts(["meeeeh-not-exists", rename_target])
437

    
438
  info = GetInstanceInfo(rename_source)
439

    
440
  # Check instance volume tags correctly updated. Note that this check is lvm
441
  # specific, so we skip it for non-lvm-based instances.
442
  # FIXME: This will need updating when instances will be able to have
443
  # different disks living on storage pools with etherogeneous storage types.
444
  # FIXME: This check should be put inside the disk/storage class themselves,
445
  # rather than explicitly called here.
446
  if info["storage-type"] == constants.ST_LVM_VG:
447
    # In the lvm world we can check for tags on the logical volume
448
    tags_cmd = ("lvs -o tags --noheadings %s | grep " %
449
                (" ".join(info["volumes"]), ))
450
  else:
451
    # Other storage types don't have tags, so we use an always failing command,
452
    # to make sure it never gets executed
453
    tags_cmd = "false"
454

    
455
  # and now rename instance to rename_target...
456
  AssertCommand(["gnt-instance", "rename", rename_source, rename_target])
457
  CheckSsconfInstanceList(rename_target)
458
  qa_utils.RunInstanceCheck(rename_source, False)
459
  qa_utils.RunInstanceCheck(rename_target, False)
460

    
461
  # NOTE: tags might not be the exactly as the instance name, due to
462
  # charset restrictions; hence the test might be flaky
463
  if (rename_source != rename_target and
464
      info["storage-type"] == constants.ST_LVM_VG):
465
    for node in info["nodes"]:
466
      AssertCommand(tags_cmd + rename_source, node=node, fail=True)
467
      AssertCommand(tags_cmd + rename_target, node=node, fail=False)
468

    
469
  # and back
470
  AssertCommand(["gnt-instance", "rename", rename_target, rename_source])
471
  CheckSsconfInstanceList(rename_source)
472
  qa_utils.RunInstanceCheck(rename_target, False)
473

    
474
  if (rename_source != rename_target and
475
      info["storage-type"] == constants.ST_LVM_VG):
476
    for node in info["nodes"]:
477
      AssertCommand(tags_cmd + rename_source, node=node, fail=False)
478
      AssertCommand(tags_cmd + rename_target, node=node, fail=True)
479

    
480

    
481
@InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
482
def TestInstanceFailover(instance):
483
  """gnt-instance failover"""
484
  if not IsFailoverSupported(instance):
485
    print qa_utils.FormatInfo("Instance doesn't support failover, skipping"
486
                              " test")
487
    return
488

    
489
  cmd = ["gnt-instance", "failover", "--force", instance.name]
490

    
491
  # failover ...
492
  AssertCommand(cmd)
493
  qa_utils.RunInstanceCheck(instance, True)
494

    
495
  # ... and back
496
  AssertCommand(cmd)
497

    
498

    
499
@InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
500
def TestInstanceMigrate(instance, toggle_always_failover=True):
501
  """gnt-instance migrate"""
502
  if not IsMigrationSupported(instance):
503
    print qa_utils.FormatInfo("Instance doesn't support migration, skipping"
504
                              " test")
505
    return
506

    
507
  cmd = ["gnt-instance", "migrate", "--force", instance.name]
508
  af_par = constants.BE_ALWAYS_FAILOVER
509
  af_field = "be/" + constants.BE_ALWAYS_FAILOVER
510
  af_init_val = _GetBoolInstanceField(instance.name, af_field)
511

    
512
  # migrate ...
513
  AssertCommand(cmd)
514
  # TODO: Verify the choice between failover and migration
515
  qa_utils.RunInstanceCheck(instance, True)
516

    
517
  # ... and back (possibly with always_failover toggled)
518
  if toggle_always_failover:
519
    AssertCommand(["gnt-instance", "modify", "-B",
520
                   ("%s=%s" % (af_par, not af_init_val)),
521
                   instance.name])
522
  AssertCommand(cmd)
523
  # TODO: Verify the choice between failover and migration
524
  qa_utils.RunInstanceCheck(instance, True)
525
  if toggle_always_failover:
526
    AssertCommand(["gnt-instance", "modify", "-B",
527
                   ("%s=%s" % (af_par, af_init_val)), instance.name])
528

    
529
  # TODO: Split into multiple tests
530
  AssertCommand(["gnt-instance", "shutdown", instance.name])
531
  qa_utils.RunInstanceCheck(instance, False)
532
  AssertCommand(cmd, fail=True)
533
  AssertCommand(["gnt-instance", "migrate", "--force", "--allow-failover",
534
                 instance.name])
535
  AssertCommand(["gnt-instance", "start", instance.name])
536
  AssertCommand(cmd)
537
  # @InstanceCheck enforces the check that the instance is running
538
  qa_utils.RunInstanceCheck(instance, True)
539

    
540
  AssertCommand(["gnt-instance", "modify", "-B",
541
                 ("%s=%s" %
542
                  (constants.BE_ALWAYS_FAILOVER, constants.VALUE_TRUE)),
543
                 instance.name])
544

    
545
  AssertCommand(cmd)
546
  qa_utils.RunInstanceCheck(instance, True)
547
  # TODO: Verify that a failover has been done instead of a migration
548

    
549
  # TODO: Verify whether the default value is restored here (not hardcoded)
550
  AssertCommand(["gnt-instance", "modify", "-B",
551
                 ("%s=%s" %
552
                  (constants.BE_ALWAYS_FAILOVER, constants.VALUE_FALSE)),
553
                 instance.name])
554

    
555
  AssertCommand(cmd)
556
  qa_utils.RunInstanceCheck(instance, True)
557

    
558

    
559
def TestInstanceInfo(instance):
560
  """gnt-instance info"""
561
  AssertCommand(["gnt-instance", "info", instance.name])
562

    
563

    
564
@InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
565
def TestInstanceModify(instance):
566
  """gnt-instance modify"""
567
  default_hv = qa_config.GetDefaultHypervisor()
568

    
569
  # Assume /sbin/init exists on all systems
570
  test_kernel = "/sbin/init"
571
  test_initrd = test_kernel
572

    
573
  orig_maxmem = qa_config.get(constants.BE_MAXMEM)
574
  orig_minmem = qa_config.get(constants.BE_MINMEM)
575
  #orig_bridge = qa_config.get("bridge", "xen-br0")
576

    
577
  args = [
578
    ["-B", "%s=128" % constants.BE_MINMEM],
579
    ["-B", "%s=128" % constants.BE_MAXMEM],
580
    ["-B", "%s=%s,%s=%s" % (constants.BE_MINMEM, orig_minmem,
581
                            constants.BE_MAXMEM, orig_maxmem)],
582
    ["-B", "%s=2" % constants.BE_VCPUS],
583
    ["-B", "%s=1" % constants.BE_VCPUS],
584
    ["-B", "%s=%s" % (constants.BE_VCPUS, constants.VALUE_DEFAULT)],
585
    ["-B", "%s=%s" % (constants.BE_ALWAYS_FAILOVER, constants.VALUE_TRUE)],
586
    ["-B", "%s=%s" % (constants.BE_ALWAYS_FAILOVER, constants.VALUE_DEFAULT)],
587

    
588
    ["-H", "%s=%s" % (constants.HV_KERNEL_PATH, test_kernel)],
589
    ["-H", "%s=%s" % (constants.HV_KERNEL_PATH, constants.VALUE_DEFAULT)],
590

    
591
    # TODO: bridge tests
592
    #["--bridge", "xen-br1"],
593
    #["--bridge", orig_bridge],
594
    ]
595

    
596
  if default_hv == constants.HT_XEN_PVM:
597
    args.extend([
598
      ["-H", "%s=%s" % (constants.HV_INITRD_PATH, test_initrd)],
599
      ["-H", "no_%s" % (constants.HV_INITRD_PATH, )],
600
      ["-H", "%s=%s" % (constants.HV_INITRD_PATH, constants.VALUE_DEFAULT)],
601
      ])
602
  elif default_hv == constants.HT_XEN_HVM:
603
    args.extend([
604
      ["-H", "%s=acn" % constants.HV_BOOT_ORDER],
605
      ["-H", "%s=%s" % (constants.HV_BOOT_ORDER, constants.VALUE_DEFAULT)],
606
      ])
607
  elif default_hv == constants.HT_KVM and \
608
    qa_config.TestEnabled("instance-device-hotplug"):
609
    args.extend([
610
      ["--net", "-1:add", "--hotplug"],
611
      ["--net", "-1:modify,mac=aa:bb:cc:dd:ee:ff", "--hotplug", "--force"],
612
      ["--net", "-1:remove", "--hotplug"],
613
      ["--disk", "-1:add,size=1G", "--hotplug"],
614
      ["--disk", "-1:remove", "--hotplug"],
615
      ])
616

    
617
  url = "http://example.com/busybox.img"
618
  args.extend([
619
      ["--os-parameters", "os-image=" + url],
620
      ["--os-parameters", "os-image=default"]
621
      ])
622

    
623
  for alist in args:
624
    AssertCommand(["gnt-instance", "modify"] + alist + [instance.name])
625

    
626
  # check no-modify
627
  AssertCommand(["gnt-instance", "modify", instance.name], fail=True)
628

    
629
  # Marking offline while instance is running must fail...
630
  AssertCommand(["gnt-instance", "modify", "--offline", instance.name],
631
                 fail=True)
632

    
633
  # ...while making it online is ok, and should work
634
  AssertCommand(["gnt-instance", "modify", "--online", instance.name])
635

    
636

    
637
@InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
638
def TestInstanceModifyPrimaryAndBack(instance, currentnode, othernode):
639
  """gnt-instance modify --new-primary
640

641
  This will leave the instance on its original primary node, not other node.
642

643
  """
644
  if instance.disk_template != constants.DT_FILE:
645
    print qa_utils.FormatInfo("Test only supported for the file disk template")
646
    return
647

    
648
  cluster_name = qa_config.get("name")
649

    
650
  name = instance.name
651
  current = currentnode.primary
652
  other = othernode.primary
653

    
654
  filestorage = qa_config.get("file-storage-dir",
655
                              pathutils.DEFAULT_FILE_STORAGE_DIR)
656
  disk = os.path.join(filestorage, name)
657

    
658
  AssertCommand(["gnt-instance", "modify", "--new-primary=%s" % other, name],
659
                fail=True)
660
  AssertCommand(["gnt-instance", "shutdown", name])
661
  AssertCommand(["scp", "-oGlobalKnownHostsFile=%s" %
662
                 pathutils.SSH_KNOWN_HOSTS_FILE,
663
                 "-oCheckHostIp=no", "-oStrictHostKeyChecking=yes",
664
                 "-oHashKnownHosts=no", "-oHostKeyAlias=%s" % cluster_name,
665
                 "-r", disk, "%s:%s" % (other, filestorage)], node=current)
666
  AssertCommand(["gnt-instance", "modify", "--new-primary=%s" % other, name])
667
  AssertCommand(["gnt-instance", "startup", name])
668

    
669
  # and back
670
  AssertCommand(["gnt-instance", "shutdown", name])
671
  AssertCommand(["rm", "-rf", disk], node=other)
672
  AssertCommand(["gnt-instance", "modify", "--new-primary=%s" % current, name])
673
  AssertCommand(["gnt-instance", "startup", name])
674

    
675

    
676
@InstanceCheck(INST_DOWN, INST_DOWN, FIRST_ARG)
677
def TestInstanceStoppedModify(instance):
678
  """gnt-instance modify (stopped instance)"""
679
  name = instance.name
680

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

    
684
  # Mark instance as offline
685
  AssertCommand(["gnt-instance", "modify", "--offline", name])
686

    
687
  # When the instance is offline shutdown should only work with --force,
688
  # while start should never work
689
  AssertCommand(["gnt-instance", "shutdown", name], fail=True)
690
  AssertCommand(["gnt-instance", "shutdown", "--force", name])
691
  AssertCommand(["gnt-instance", "start", name], fail=True)
692
  AssertCommand(["gnt-instance", "start", "--force", name], fail=True)
693

    
694
  # Also do offline to offline
695
  AssertCommand(["gnt-instance", "modify", "--offline", name])
696

    
697
  # And online again
698
  AssertCommand(["gnt-instance", "modify", "--online", name])
699

    
700

    
701
@InstanceCheck(INST_DOWN, INST_DOWN, FIRST_ARG)
702
def TestInstanceConvertDiskToPlain(instance, inodes):
703
  """gnt-instance modify -t"""
704
  name = instance.name
705

    
706
  template = instance.disk_template
707
  if template != constants.DT_DRBD8:
708
    print qa_utils.FormatInfo("Unsupported template %s, skipping conversion"
709
                              " test" % template)
710
    return
711

    
712
  assert len(inodes) == 2
713
  AssertCommand(["gnt-instance", "modify", "-t", constants.DT_PLAIN, name])
714
  AssertCommand(["gnt-instance", "modify", "-t", constants.DT_DRBD8,
715
                 "-n", inodes[1].primary, name])
716

    
717

    
718
@InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
719
def TestInstanceModifyDisks(instance):
720
  """gnt-instance modify --disk"""
721
  if not IsDiskSupported(instance):
722
    print qa_utils.FormatInfo("Instance doesn't support disks, skipping test")
723
    return
724

    
725
  disk_conf = qa_config.GetDiskOptions()[-1]
726
  size = disk_conf.get("size")
727
  name = instance.name
728
  build_cmd = lambda arg: ["gnt-instance", "modify", "--disk", arg, name]
729
  if qa_config.AreSpindlesSupported():
730
    spindles = disk_conf.get("spindles")
731
    spindles_supported = True
732
  else:
733
    # Any number is good for spindles in this case
734
    spindles = 1
735
    spindles_supported = False
736
  AssertCommand(build_cmd("add:size=%s,spindles=%s" % (size, spindles)),
737
                fail=not spindles_supported)
738
  AssertCommand(build_cmd("add:size=%s" % size),
739
                fail=spindles_supported)
740
  # Exactly one of the above commands has succeded, so we need one remove
741
  AssertCommand(build_cmd("remove"))
742

    
743

    
744
@InstanceCheck(INST_DOWN, INST_DOWN, FIRST_ARG)
745
def TestInstanceGrowDisk(instance):
746
  """gnt-instance grow-disk"""
747
  if instance.disk_template == constants.DT_DISKLESS:
748
    print qa_utils.FormatInfo("Test not supported for diskless instances")
749
    return
750

    
751
  name = instance.name
752
  disks = qa_config.GetDiskOptions()
753
  all_size = [d.get("size") for d in disks]
754
  all_grow = [d.get("growth") for d in disks]
755

    
756
  if not all_grow:
757
    # missing disk sizes but instance grow disk has been enabled,
758
    # let's set fixed/nomimal growth
759
    all_grow = ["128M" for _ in all_size]
760

    
761
  for idx, (size, grow) in enumerate(zip(all_size, all_grow)):
762
    # succeed in grow by amount
763
    AssertCommand(["gnt-instance", "grow-disk", name, str(idx), grow])
764
    # fail in grow to the old size
765
    AssertCommand(["gnt-instance", "grow-disk", "--absolute", name, str(idx),
766
                   size], fail=True)
767
    # succeed to grow to old size + 2 * growth
768
    int_size = utils.ParseUnit(size)
769
    int_grow = utils.ParseUnit(grow)
770
    AssertCommand(["gnt-instance", "grow-disk", "--absolute", name, str(idx),
771
                   str(int_size + 2 * int_grow)])
772

    
773

    
774
@InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
775
def TestInstanceDeviceNames(instance):
776
  if instance.disk_template == constants.DT_DISKLESS:
777
    print qa_utils.FormatInfo("Test not supported for diskless instances")
778
    return
779

    
780
  name = instance.name
781
  for dev_type in ["disk", "net"]:
782
    if dev_type == "disk":
783
      options = ",size=512M"
784
      if qa_config.AreSpindlesSupported():
785
        options += ",spindles=1"
786
    else:
787
      options = ""
788
    # succeed in adding a device named 'test_device'
789
    AssertCommand(["gnt-instance", "modify",
790
                   "--%s=-1:add,name=test_device%s" % (dev_type, options),
791
                   name])
792
    # succeed in removing the 'test_device'
793
    AssertCommand(["gnt-instance", "modify",
794
                   "--%s=test_device:remove" % dev_type,
795
                   name])
796
    # fail to add two devices with the same name
797
    AssertCommand(["gnt-instance", "modify",
798
                   "--%s=-1:add,name=test_device%s" % (dev_type, options),
799
                   "--%s=-1:add,name=test_device%s" % (dev_type, options),
800
                   name], fail=True)
801
    # fail to add a device with invalid name
802
    AssertCommand(["gnt-instance", "modify",
803
                   "--%s=-1:add,name=2%s" % (dev_type, options),
804
                   name], fail=True)
805
  # Rename disks
806
  disks = qa_config.GetDiskOptions()
807
  disk_names = [d.get("name") for d in disks]
808
  for idx, disk_name in enumerate(disk_names):
809
    # Refer to disk by idx
810
    AssertCommand(["gnt-instance", "modify",
811
                   "--disk=%s:modify,name=renamed" % idx,
812
                   name])
813
    # Refer to by name and rename to original name
814
    AssertCommand(["gnt-instance", "modify",
815
                   "--disk=renamed:modify,name=%s" % disk_name,
816
                   name])
817
  if len(disks) >= 2:
818
    # fail in renaming to disks to the same name
819
    AssertCommand(["gnt-instance", "modify",
820
                   "--disk=0:modify,name=same_name",
821
                   "--disk=1:modify,name=same_name",
822
                   name], fail=True)
823

    
824

    
825
def TestInstanceList():
826
  """gnt-instance list"""
827
  qa_utils.GenericQueryTest("gnt-instance", query.INSTANCE_FIELDS.keys())
828

    
829

    
830
def TestInstanceListFields():
831
  """gnt-instance list-fields"""
832
  qa_utils.GenericQueryFieldsTest("gnt-instance", query.INSTANCE_FIELDS.keys())
833

    
834

    
835
@InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
836
def TestInstanceConsole(instance):
837
  """gnt-instance console"""
838
  AssertCommand(["gnt-instance", "console", "--show-cmd", instance.name])
839

    
840

    
841
@InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
842
def TestReplaceDisks(instance, curr_nodes, other_nodes):
843
  """gnt-instance replace-disks"""
844
  def buildcmd(args):
845
    cmd = ["gnt-instance", "replace-disks"]
846
    cmd.extend(args)
847
    cmd.append(instance.name)
848
    return cmd
849

    
850
  if not IsDiskReplacingSupported(instance):
851
    print qa_utils.FormatInfo("Instance doesn't support disk replacing,"
852
                              " skipping test")
853
    return
854

    
855
  # Currently all supported templates have one primary and one secondary node
856
  assert len(curr_nodes) == 2
857
  snode = curr_nodes[1]
858
  assert len(other_nodes) == 1
859
  othernode = other_nodes[0]
860

    
861
  options = qa_config.get("options", {})
862
  use_ialloc = options.get("use-iallocators", True)
863
  for data in [
864
    ["-p"],
865
    ["-s"],
866
    # A placeholder; the actual command choice depends on use_ialloc
867
    None,
868
    # Restore the original secondary
869
    ["--new-secondary=%s" % snode.primary],
870
    ]:
871
    if data is None:
872
      if use_ialloc:
873
        data = ["-I", constants.DEFAULT_IALLOCATOR_SHORTCUT]
874
      else:
875
        data = ["--new-secondary=%s" % othernode.primary]
876
    AssertCommand(buildcmd(data))
877

    
878
  AssertCommand(buildcmd(["-a"]))
879
  AssertCommand(["gnt-instance", "stop", instance.name])
880
  AssertCommand(buildcmd(["-a"]), fail=True)
881
  AssertCommand(["gnt-instance", "activate-disks", instance.name])
882
  AssertCommand(["gnt-instance", "activate-disks", "--wait-for-sync",
883
                 instance.name])
884
  AssertCommand(buildcmd(["-a"]))
885
  AssertCommand(["gnt-instance", "start", instance.name])
886

    
887

    
888
def _AssertRecreateDisks(cmdargs, instance, fail=False, check=True,
889
                         destroy=True):
890
  """Execute gnt-instance recreate-disks and check the result
891

892
  @param cmdargs: Arguments (instance name excluded)
893
  @param instance: Instance to operate on
894
  @param fail: True if the command is expected to fail
895
  @param check: If True and fail is False, check that the disks work
896
  @prama destroy: If True, destroy the old disks first
897

898
  """
899
  if destroy:
900
    _DestroyInstanceDisks(instance)
901
  AssertCommand((["gnt-instance", "recreate-disks"] + cmdargs +
902
                 [instance.name]), fail)
903
  if not fail and check:
904
    # Quick check that the disks are there
905
    AssertCommand(["gnt-instance", "activate-disks", instance.name])
906
    AssertCommand(["gnt-instance", "activate-disks", "--wait-for-sync",
907
                   instance.name])
908
    AssertCommand(["gnt-instance", "deactivate-disks", instance.name])
909

    
910

    
911
def _BuildRecreateDisksOpts(en_disks, with_spindles, with_growth,
912
                            spindles_supported):
913
  if with_spindles:
914
    if spindles_supported:
915
      if with_growth:
916
        build_spindles_opt = (lambda disk:
917
                              ",spindles=%s" %
918
                              (disk["spindles"] + disk["spindles-growth"]))
919
      else:
920
        build_spindles_opt = (lambda disk:
921
                              ",spindles=%s" % disk["spindles"])
922
    else:
923
      build_spindles_opt = (lambda _: ",spindles=1")
924
  else:
925
    build_spindles_opt = (lambda _: "")
926
  if with_growth:
927
    build_size_opt = (lambda disk:
928
                      "size=%s" % (utils.ParseUnit(disk["size"]) +
929
                                   utils.ParseUnit(disk["growth"])))
930
  else:
931
    build_size_opt = (lambda disk: "size=%s" % disk["size"])
932
  build_disk_opt = (lambda (idx, disk):
933
                    "--disk=%s:%s%s" % (idx, build_size_opt(disk),
934
                                        build_spindles_opt(disk)))
935
  return map(build_disk_opt, en_disks)
936

    
937

    
938
@InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
939
def TestRecreateDisks(instance, inodes, othernodes):
940
  """gnt-instance recreate-disks
941

942
  @param instance: Instance to work on
943
  @param inodes: List of the current nodes of the instance
944
  @param othernodes: list/tuple of nodes where to temporarily recreate disks
945

946
  """
947
  options = qa_config.get("options", {})
948
  use_ialloc = options.get("use-iallocators", True)
949
  other_seq = ":".join([n.primary for n in othernodes])
950
  orig_seq = ":".join([n.primary for n in inodes])
951
  # These fail because the instance is running
952
  _AssertRecreateDisks(["-n", other_seq], instance, fail=True, destroy=False)
953
  if use_ialloc:
954
    _AssertRecreateDisks(["-I", "hail"], instance, fail=True, destroy=False)
955
  else:
956
    _AssertRecreateDisks(["-n", other_seq], instance, fail=True, destroy=False)
957
  AssertCommand(["gnt-instance", "stop", instance.name])
958
  # Disks exist: this should fail
959
  _AssertRecreateDisks([], instance, fail=True, destroy=False)
960
  # Unsupported spindles parameters: fail
961
  if not qa_config.AreSpindlesSupported():
962
    _AssertRecreateDisks(["--disk=0:spindles=2"], instance,
963
                         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)
976
  # Recreate resized disks
977
  # One of the two commands fails because either spindles are given when they
978
  # should not or vice versa
979
  alldisks = qa_config.GetDiskOptions()
980
  spindles_supported = qa_config.AreSpindlesSupported()
981
  disk_opts = _BuildRecreateDisksOpts(enumerate(alldisks), True, True,
982
                                      spindles_supported)
983
  _AssertRecreateDisks(disk_opts, instance, destroy=True,
984
                       fail=not spindles_supported)
985
  disk_opts = _BuildRecreateDisksOpts(enumerate(alldisks), False, True,
986
                                      spindles_supported)
987
  _AssertRecreateDisks(disk_opts, instance, destroy=False,
988
                       fail=spindles_supported)
989
  # Recreate the disks one by one (with the original size)
990
  for (idx, disk) in enumerate(alldisks):
991
    # Only the first call should destroy all the disk
992
    destroy = (idx == 0)
993
    # Again, one of the two commands is expected to fail
994
    disk_opts = _BuildRecreateDisksOpts([(idx, disk)], True, False,
995
                                        spindles_supported)
996
    _AssertRecreateDisks(disk_opts, instance, destroy=destroy, check=False,
997
                         fail=not spindles_supported)
998
    disk_opts = _BuildRecreateDisksOpts([(idx, disk)], False, False,
999
                                        spindles_supported)
1000
    _AssertRecreateDisks(disk_opts, instance, destroy=False, check=False,
1001
                         fail=spindles_supported)
1002
  # This and InstanceCheck decoration check that the disks are working
1003
  AssertCommand(["gnt-instance", "reinstall", "-f", instance.name])
1004
  AssertCommand(["gnt-instance", "start", instance.name])
1005

    
1006

    
1007
@InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
1008
def TestInstanceExport(instance, node):
1009
  """gnt-backup export -n ..."""
1010
  name = instance.name
1011
  # Export does not work for file-based templates, thus we skip the test
1012
  if instance.disk_template in [constants.DT_FILE, constants.DT_SHARED_FILE]:
1013
    return
1014
  AssertCommand(["gnt-backup", "export", "-n", node.primary, name])
1015
  return qa_utils.ResolveInstanceName(name)
1016

    
1017

    
1018
@InstanceCheck(None, INST_DOWN, FIRST_ARG)
1019
def TestInstanceExportWithRemove(instance, node):
1020
  """gnt-backup export --remove-instance"""
1021
  AssertCommand(["gnt-backup", "export", "-n", node.primary,
1022
                 "--remove-instance", instance.name])
1023

    
1024

    
1025
@InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
1026
def TestInstanceExportNoTarget(instance):
1027
  """gnt-backup export (without target node, should fail)"""
1028
  AssertCommand(["gnt-backup", "export", instance.name], fail=True)
1029

    
1030

    
1031
@InstanceCheck(None, INST_DOWN, FIRST_ARG)
1032
def TestInstanceImport(newinst, node, expnode, name):
1033
  """gnt-backup import"""
1034
  templ = constants.DT_PLAIN
1035
  if not qa_config.IsTemplateSupported(templ):
1036
    return
1037
  cmd = (["gnt-backup", "import",
1038
          "--disk-template=%s" % templ,
1039
          "--no-ip-check",
1040
          "--src-node=%s" % expnode.primary,
1041
          "--src-dir=%s/%s" % (pathutils.EXPORT_DIR, name),
1042
          "--node=%s" % node.primary] +
1043
         GetGenericAddParameters(newinst, templ,
1044
                                  force_mac=constants.VALUE_GENERATE))
1045
  cmd.append(newinst.name)
1046
  AssertCommand(cmd)
1047
  newinst.SetDiskTemplate(templ)
1048

    
1049

    
1050
def TestBackupList(expnode):
1051
  """gnt-backup list"""
1052
  AssertCommand(["gnt-backup", "list", "--node=%s" % expnode.primary])
1053

    
1054
  qa_utils.GenericQueryTest("gnt-backup", query.EXPORT_FIELDS.keys(),
1055
                            namefield=None, test_unknown=False)
1056

    
1057

    
1058
def TestBackupListFields():
1059
  """gnt-backup list-fields"""
1060
  qa_utils.GenericQueryFieldsTest("gnt-backup", query.EXPORT_FIELDS.keys())
1061

    
1062

    
1063
def TestRemoveInstanceOfflineNode(instance, snode, set_offline, set_online):
1064
  """gnt-instance remove with an off-line node
1065

1066
  @param instance: instance
1067
  @param snode: secondary node, to be set offline
1068
  @param set_offline: function to call to set the node off-line
1069
  @param set_online: function to call to set the node on-line
1070

1071
  """
1072
  info = GetInstanceInfo(instance.name)
1073
  set_offline(snode)
1074
  try:
1075
    TestInstanceRemove(instance)
1076
  finally:
1077
    set_online(snode)
1078

    
1079
  # Clean up the disks on the offline node, if necessary
1080
  if instance.disk_template not in constants.DTS_EXT_MIRROR:
1081
    # FIXME: abstract the cleanup inside the disks
1082
    if info["storage-type"] == constants.ST_LVM_VG:
1083
      for minor in info["drbd-minors"][snode.primary]:
1084
        # DRBD 8.3 syntax comes first, then DRBD 8.4 syntax. The 8.4 syntax
1085
        # relies on the fact that we always create a resources for each minor,
1086
        # and that this resources is always named resource{minor}.
1087
        # As 'drbdsetup 0 down' does return success (even though that's invalid
1088
        # syntax), we always have to perform both commands and ignore the
1089
        # output.
1090
        drbd_shutdown_cmd = \
1091
          "(drbdsetup %d down >/dev/null 2>&1;" \
1092
          " drbdsetup down resource%d >/dev/null 2>&1) || /bin/true" % \
1093
            (minor, minor)
1094
        AssertCommand(drbd_shutdown_cmd, node=snode)
1095
      AssertCommand(["lvremove", "-f"] + info["volumes"], node=snode)
1096
    elif info["storage-type"] == constants.ST_FILE:
1097
      filestorage = qa_config.get("file-storage-dir",
1098
                                  pathutils.DEFAULT_FILE_STORAGE_DIR)
1099
      disk = os.path.join(filestorage, instance.name)
1100
      AssertCommand(["rm", "-rf", disk], node=snode)
1101

    
1102

    
1103
def TestInstanceCreationRestrictedByDiskTemplates():
1104
  """Test adding instances for disabled disk templates."""
1105
  if qa_config.TestEnabled("cluster-exclusive-storage"):
1106
    # These tests are valid only for non-exclusive storage
1107
    return
1108

    
1109
  enabled_disk_templates = qa_config.GetEnabledDiskTemplates()
1110
  nodes = qa_config.AcquireManyNodes(2)
1111

    
1112
  # Setup the cluster with the enabled_disk_templates
1113
  AssertCommand(
1114
    ["gnt-cluster", "modify",
1115
     "--enabled-disk-templates=%s" % ",".join(enabled_disk_templates),
1116
     "--ipolicy-disk-templates=%s" % ",".join(enabled_disk_templates)],
1117
    fail=False)
1118

    
1119
  # Test instance creation for enabled disk templates
1120
  for disk_template in enabled_disk_templates:
1121
    instance = CreateInstanceByDiskTemplate(nodes, disk_template, fail=False)
1122
    TestInstanceRemove(instance)
1123
    instance.Release()
1124

    
1125
  # Test that instance creation fails for disabled disk templates
1126
  disabled_disk_templates = list(constants.DISK_TEMPLATES
1127
                                 - set(enabled_disk_templates))
1128
  for disk_template in disabled_disk_templates:
1129
    instance = CreateInstanceByDiskTemplate(nodes, disk_template, fail=True)
1130

    
1131
  # Test instance creation for after disabling enabled disk templates
1132
  if (len(enabled_disk_templates) > 1):
1133
    # Partition the disk templates, enable them separately and check if the
1134
    # disabled ones cannot be used by instances.
1135
    middle = len(enabled_disk_templates) / 2
1136
    templates1 = enabled_disk_templates[:middle]
1137
    templates2 = enabled_disk_templates[middle:]
1138

    
1139
    for (enabled, disabled) in [(templates1, templates2),
1140
                                (templates2, templates1)]:
1141
      AssertCommand(["gnt-cluster", "modify",
1142
                     "--enabled-disk-templates=%s" % ",".join(enabled),
1143
                     "--ipolicy-disk-templates=%s" % ",".join(enabled)],
1144
                    fail=False)
1145
      for disk_template in disabled:
1146
        CreateInstanceByDiskTemplate(nodes, disk_template, fail=True)
1147
  elif (len(enabled_disk_templates) == 1):
1148
    # If only one disk template is enabled in the QA config, we have to enable
1149
    # some other templates in order to test if the disabling the only enabled
1150
    # disk template prohibits creating instances of that template.
1151
    other_disk_templates = list(
1152
                             set([constants.DT_DISKLESS, constants.DT_BLOCK]) -
1153
                             set(enabled_disk_templates))
1154
    AssertCommand(["gnt-cluster", "modify",
1155
                   "--enabled-disk-templates=%s" %
1156
                     ",".join(other_disk_templates),
1157
                   "--ipolicy-disk-templates=%s" %
1158
                     ",".join(other_disk_templates)],
1159
                  fail=False)
1160
    CreateInstanceByDiskTemplate(nodes, enabled_disk_templates[0], fail=True)
1161
  else:
1162
    raise qa_error.Error("Please enable at least one disk template"
1163
                         " in your QA setup.")
1164

    
1165
  # Restore initially enabled disk templates
1166
  AssertCommand(["gnt-cluster", "modify",
1167
                 "--enabled-disk-templates=%s" %
1168
                   ",".join(enabled_disk_templates),
1169
                 "--ipolicy-disk-templates=%s" %
1170
                   ",".join(enabled_disk_templates)],
1171
                 fail=False)
1172

    
1173

    
1174
@InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
1175
def _TestInstanceUserDown(instance, master, hv_shutdown_fn):
1176
  # Shutdown instance and bring instance status to 'USER_down'
1177
  hv_shutdown_fn()
1178

    
1179
  cmd = ["gnt-instance", "list", "--no-headers", "-o", "status", instance.name]
1180
  result_output = qa_utils.GetCommandOutput(master.primary,
1181
                                            utils.ShellQuoteArgs(cmd))
1182
  AssertEqual(result_output.strip(), constants.INSTST_USERDOWN)
1183

    
1184
  # Fail to bring instance status to 'running'
1185
  AssertCommand(["gnt-instance", "start", instance.name], fail=True)
1186

    
1187
  cmd = ["gnt-instance", "list", "--no-headers", "-o", "status", instance.name]
1188
  result_output = qa_utils.GetCommandOutput(master.primary,
1189
                                            utils.ShellQuoteArgs(cmd))
1190
  AssertEqual(result_output.strip(), constants.INSTST_USERDOWN)
1191

    
1192
  # Bring instance status to 'ADMIN_down'
1193
  AssertCommand(["gnt-instance", "shutdown", instance.name])
1194

    
1195
  cmd = ["gnt-instance", "list", "--no-headers", "-o", "status", instance.name]
1196
  result_output = qa_utils.GetCommandOutput(master.primary,
1197
                                            utils.ShellQuoteArgs(cmd))
1198
  AssertEqual(result_output.strip(), constants.INSTST_ADMINDOWN)
1199

    
1200
  # Bring instance status to 'running'
1201
  AssertCommand(["gnt-instance", "start", instance.name])
1202

    
1203
  cmd = ["gnt-instance", "list", "--no-headers", "-o", "status", instance.name]
1204
  result_output = qa_utils.GetCommandOutput(master.primary,
1205
                                            utils.ShellQuoteArgs(cmd))
1206
  AssertEqual(result_output.strip(), constants.INSTST_RUNNING)
1207

    
1208
  # Bring instance status to 'ADMIN_down' forcibly
1209
  AssertCommand(["gnt-instance", "shutdown", "-f", instance.name])
1210

    
1211
  cmd = ["gnt-instance", "list", "--no-headers", "-o", "status", instance.name]
1212
  result_output = qa_utils.GetCommandOutput(master.primary,
1213
                                            utils.ShellQuoteArgs(cmd))
1214
  AssertEqual(result_output.strip(), constants.INSTST_ADMINDOWN)
1215

    
1216
  # Bring instance status to 'running'
1217
  AssertCommand(["gnt-instance", "start", instance.name])
1218

    
1219
  cmd = ["gnt-instance", "list", "--no-headers", "-o", "status", instance.name]
1220
  result_output = qa_utils.GetCommandOutput(master.primary,
1221
                                            utils.ShellQuoteArgs(cmd))
1222
  AssertEqual(result_output.strip(), constants.INSTST_RUNNING)
1223

    
1224

    
1225
@InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
1226
def _TestInstanceUserDownXen(instance, master):
1227
  primary = _GetInstanceField(instance.name, "pnode")
1228
  fn = lambda: AssertCommand(["xm", "shutdown", "-w", instance.name],
1229
                             node=primary)
1230
  _TestInstanceUserDown(instance, master, fn)
1231

    
1232

    
1233
@InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
1234
def _TestInstanceUserDownKvm(instance, master):
1235
  def _StopKVMInstance():
1236
    AssertCommand("pkill -f \"\\-name %s\"" % instance.name, node=primary)
1237
    time.sleep(10)
1238

    
1239
  AssertCommand(["gnt-instance", "modify", "-H", "user_shutdown=true",
1240
                 instance.name])
1241

    
1242
  # The instance needs to reboot not because the 'user_shutdown'
1243
  # parameter was modified but because the KVM daemon need to be
1244
  # started, given that the instance was first created with user
1245
  # shutdown disabled.
1246
  AssertCommand(["gnt-instance", "reboot", instance.name])
1247

    
1248
  primary = _GetInstanceField(instance.name, "pnode")
1249
  _TestInstanceUserDown(instance, master, _StopKVMInstance)
1250

    
1251

    
1252
def TestInstanceUserDown(instance, master):
1253
  """Tests user shutdown"""
1254
  enabled_hypervisors = qa_config.GetEnabledHypervisors()
1255

    
1256
  for (hv, fn) in [(constants.HT_XEN_PVM, _TestInstanceUserDownXen),
1257
                   (constants.HT_XEN_HVM, _TestInstanceUserDownXen),
1258
                   (constants.HT_KVM, _TestInstanceUserDownKvm)]:
1259
    if hv in enabled_hypervisors:
1260
      qa_daemon.TestPauseWatcher()
1261
      fn(instance, master)
1262
      qa_daemon.TestResumeWatcher()
1263
    else:
1264
      print "%s hypervisor is not enabled, skipping test for this hypervisor" \
1265
          % hv
1266

    
1267

    
1268
@InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
1269
def TestInstanceCommunication(instance, master):
1270
  """Tests instance communication via 'gnt-instance modify'"""
1271

    
1272
  # Enable instance communication network at the cluster level
1273
  network_name = "mynetwork"
1274

    
1275
  cmd = ["gnt-cluster", "modify",
1276
         "--instance-communication-network=%s" % network_name]
1277
  result_output = qa_utils.GetCommandOutput(master.primary,
1278
                                            utils.ShellQuoteArgs(cmd))
1279
  print result_output
1280

    
1281
  # Enable instance communication mechanism for this instance
1282
  AssertCommand(["gnt-instance", "modify", "-c", "yes", instance.name])
1283

    
1284
  # Reboot instance for changes to NIC to take effect
1285
  AssertCommand(["gnt-instance", "reboot", instance.name])
1286

    
1287
  # Check if the instance is properly configured for instance
1288
  # communication.
1289
  nic_name = "%s%s" % (constants.INSTANCE_COMMUNICATION_NIC_PREFIX,
1290
                       instance.name)
1291

    
1292
  ## Check the output of 'gnt-instance list'
1293
  nic_names = _GetInstanceField(instance.name, "nic.names")
1294
  nic_names = map(lambda x: x.strip(" '"), nic_names.strip("[]").split(","))
1295

    
1296
  AssertIn(nic_name, nic_names,
1297
           msg="Looking for instance communication TAP interface")
1298

    
1299
  nic_n = nic_names.index(nic_name)
1300

    
1301
  nic_ip = _GetInstanceField(instance.name, "nic.ip/%d" % nic_n)
1302
  nic_network = _GetInstanceField(instance.name, "nic.network.name/%d" % nic_n)
1303
  nic_mode = _GetInstanceField(instance.name, "nic.mode/%d" % nic_n)
1304

    
1305
  AssertEqual(IP4Address.InNetwork(constants.INSTANCE_COMMUNICATION_NETWORK4,
1306
                                   nic_ip),
1307
              True,
1308
              msg="Checking if NIC's IP if part of the expected network")
1309

    
1310
  AssertEqual(network_name, nic_network,
1311
              msg="Checking if NIC's network name matches the expected value")
1312

    
1313
  AssertEqual(constants.INSTANCE_COMMUNICATION_NETWORK_MODE, nic_mode,
1314
              msg="Checking if NIC's mode name matches the expected value")
1315

    
1316
  ## Check the output of 'ip route'
1317
  cmd = ["ip", "route", "show", nic_ip]
1318
  result_output = qa_utils.GetCommandOutput(master.primary,
1319
                                            utils.ShellQuoteArgs(cmd))
1320
  result = result_output.split()
1321

    
1322
  AssertEqual(len(result), 5, msg="Checking if the IP route is established")
1323

    
1324
  route_ip = result[0]
1325
  route_dev = result[1]
1326
  route_tap = result[2]
1327
  route_scope = result[3]
1328
  route_link = result[4]
1329

    
1330
  AssertEqual(route_ip, nic_ip,
1331
              msg="Checking if IP route shows the expected IP")
1332
  AssertEqual(route_dev, "dev",
1333
              msg="Checking if IP route shows the expected device")
1334
  AssertEqual(route_scope, "scope",
1335
              msg="Checking if IP route shows the expected scope")
1336
  AssertEqual(route_link, "link",
1337
              msg="Checking if IP route shows the expected link-level scope")
1338

    
1339
  ## Check the output of 'ip address'
1340
  cmd = ["ip", "address", "show", "dev", route_tap]
1341
  result_output = qa_utils.GetCommandOutput(master.primary,
1342
                                            utils.ShellQuoteArgs(cmd))
1343
  result = result_output.splitlines()
1344

    
1345
  AssertEqual(len(result), 3,
1346
              msg="Checking if the IP address is established")
1347

    
1348
  result = result.pop().split()
1349

    
1350
  AssertEqual(len(result), 7,
1351
              msg="Checking if the IP address has the expected value")
1352

    
1353
  address_ip = result[1]
1354
  address_netmask = result[3]
1355

    
1356
  AssertEqual(address_ip, "169.254.169.254/32",
1357
              msg="Checking if the TAP interface has the expected IP")
1358
  AssertEqual(address_netmask, "169.254.255.255",
1359
              msg="Checking if the TAP interface has the expected netmask")
1360

    
1361
  # Disable instance communication mechanism for this instance
1362
  AssertCommand(["gnt-instance", "modify", "-c", "no", instance.name])
1363

    
1364
  # Reboot instance for changes to NIC to take effect
1365
  AssertCommand(["gnt-instance", "reboot", instance.name])
1366

    
1367
  # Disable instance communication network at cluster level
1368
  cmd = ["gnt-cluster", "modify",
1369
         "--instance-communication-network=%s" % network_name]
1370
  result_output = qa_utils.GetCommandOutput(master.primary,
1371
                                            utils.ShellQuoteArgs(cmd))
1372
  print result_output
1373

    
1374

    
1375
available_instance_tests = [
1376
  ("instance-add-plain-disk", constants.DT_PLAIN,
1377
   TestInstanceAddWithPlainDisk, 1),
1378
  ("instance-add-drbd-disk", constants.DT_DRBD8,
1379
   TestInstanceAddWithDrbdDisk, 2),
1380
  ("instance-add-diskless", constants.DT_DISKLESS,
1381
   TestInstanceAddDiskless, 1),
1382
  ("instance-add-file", constants.DT_FILE,
1383
   TestInstanceAddFile, 1),
1384
  ("instance-add-shared-file", constants.DT_SHARED_FILE,
1385
   TestInstanceAddSharedFile, 1),
1386
  ]