Statistics
| Branch: | Tag: | Revision:

root / qa / qa_instance.py @ 67bd83ae

History | View | Annotate | Download (43.9 kB)

1
#
2
#
3

    
4
# Copyright (C) 2007, 2011, 2012, 2013 Google Inc.
5
#
6
# This program is free software; you can redistribute it and/or modify
7
# it under the terms of the GNU General Public License as published by
8
# the Free Software Foundation; either version 2 of the License, or
9
# (at your option) any later version.
10
#
11
# This program is distributed in the hope that it will be useful, but
12
# WITHOUT ANY WARRANTY; without even the implied warranty of
13
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14
# General Public License for more details.
15
#
16
# You should have received a copy of the GNU General Public License
17
# along with this program; if not, write to the Free Software
18
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19
# 02110-1301, USA.
20

    
21

    
22
"""Instance related QA tests.
23

24
"""
25

    
26
import os
27
import re
28
import time
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_daemon
37
import qa_utils
38
import qa_error
39

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

    
48

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

    
52

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

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

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

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

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

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

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

    
124

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

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

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

    
150

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

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

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

    
166

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

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

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

    
186

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

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

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

    
208

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

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

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

    
239

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

    
243

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

    
247

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

    
251

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

    
255

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

    
265

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

    
272

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

    
280

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

    
288

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

    
296

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

    
302

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

    
308

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

    
314

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

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

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

    
334

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

    
342
  AssertCommand(["gnt-instance", "reinstall", "-f", instance.name])
343

    
344
  # Test with non-existant OS definition
345
  AssertCommand(["gnt-instance", "reinstall", "-f",
346
                 "--os-type=NonExistantOsForQa",
347
                 instance.name],
348
                fail=True)
349

    
350

    
351
@InstanceCheck(INST_DOWN, INST_DOWN, FIRST_ARG)
352
def TestInstanceRenameAndBack(rename_source, rename_target):
353
  """gnt-instance rename
354

355
  This must leave the instance with the original name, not the target
356
  name.
357

358
  """
359
  CheckSsconfInstanceList(rename_source)
360

    
361
  # first do a rename to a different actual name, expecting it to fail
362
  qa_utils.AddToEtcHosts(["meeeeh-not-exists", rename_target])
363
  try:
364
    AssertCommand(["gnt-instance", "rename", rename_source, rename_target],
365
                  fail=True)
366
    CheckSsconfInstanceList(rename_source)
367
  finally:
368
    qa_utils.RemoveFromEtcHosts(["meeeeh-not-exists", rename_target])
369

    
370
  info = GetInstanceInfo(rename_source)
371

    
372
  # Check instance volume tags correctly updated. Note that this check is lvm
373
  # specific, so we skip it for non-lvm-based instances.
374
  # FIXME: This will need updating when instances will be able to have
375
  # different disks living on storage pools with etherogeneous storage types.
376
  # FIXME: This check should be put inside the disk/storage class themselves,
377
  # rather than explicitly called here.
378
  if info["storage-type"] == constants.ST_LVM_VG:
379
    # In the lvm world we can check for tags on the logical volume
380
    tags_cmd = ("lvs -o tags --noheadings %s | grep " %
381
                (" ".join(info["volumes"]), ))
382
  else:
383
    # Other storage types don't have tags, so we use an always failing command,
384
    # to make sure it never gets executed
385
    tags_cmd = "false"
386

    
387
  # and now rename instance to rename_target...
388
  AssertCommand(["gnt-instance", "rename", rename_source, rename_target])
389
  CheckSsconfInstanceList(rename_target)
390
  qa_utils.RunInstanceCheck(rename_source, False)
391
  qa_utils.RunInstanceCheck(rename_target, False)
392

    
393
  # NOTE: tags might not be the exactly as the instance name, due to
394
  # charset restrictions; hence the test might be flaky
395
  if (rename_source != rename_target and
396
      info["storage-type"] == constants.ST_LVM_VG):
397
    for node in info["nodes"]:
398
      AssertCommand(tags_cmd + rename_source, node=node, fail=True)
399
      AssertCommand(tags_cmd + rename_target, node=node, fail=False)
400

    
401
  # and back
402
  AssertCommand(["gnt-instance", "rename", rename_target, rename_source])
403
  CheckSsconfInstanceList(rename_source)
404
  qa_utils.RunInstanceCheck(rename_target, False)
405

    
406
  if (rename_source != rename_target and
407
      info["storage-type"] == constants.ST_LVM_VG):
408
    for node in info["nodes"]:
409
      AssertCommand(tags_cmd + rename_source, node=node, fail=False)
410
      AssertCommand(tags_cmd + rename_target, node=node, fail=True)
411

    
412

    
413
@InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
414
def TestInstanceFailover(instance):
415
  """gnt-instance failover"""
416
  if not IsFailoverSupported(instance):
417
    print qa_utils.FormatInfo("Instance doesn't support failover, skipping"
418
                              " test")
419
    return
420

    
421
  cmd = ["gnt-instance", "failover", "--force", instance.name]
422

    
423
  # failover ...
424
  AssertCommand(cmd)
425
  qa_utils.RunInstanceCheck(instance, True)
426

    
427
  # ... and back
428
  AssertCommand(cmd)
429

    
430

    
431
@InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
432
def TestInstanceMigrate(instance, toggle_always_failover=True):
433
  """gnt-instance migrate"""
434
  if not IsMigrationSupported(instance):
435
    print qa_utils.FormatInfo("Instance doesn't support migration, skipping"
436
                              " test")
437
    return
438

    
439
  cmd = ["gnt-instance", "migrate", "--force", instance.name]
440
  af_par = constants.BE_ALWAYS_FAILOVER
441
  af_field = "be/" + constants.BE_ALWAYS_FAILOVER
442
  af_init_val = _GetBoolInstanceField(instance.name, af_field)
443

    
444
  # migrate ...
445
  AssertCommand(cmd)
446
  # TODO: Verify the choice between failover and migration
447
  qa_utils.RunInstanceCheck(instance, True)
448

    
449
  # ... and back (possibly with always_failover toggled)
450
  if toggle_always_failover:
451
    AssertCommand(["gnt-instance", "modify", "-B",
452
                   ("%s=%s" % (af_par, not af_init_val)),
453
                   instance.name])
454
  AssertCommand(cmd)
455
  # TODO: Verify the choice between failover and migration
456
  qa_utils.RunInstanceCheck(instance, True)
457
  if toggle_always_failover:
458
    AssertCommand(["gnt-instance", "modify", "-B",
459
                   ("%s=%s" % (af_par, af_init_val)), instance.name])
460

    
461
  # TODO: Split into multiple tests
462
  AssertCommand(["gnt-instance", "shutdown", instance.name])
463
  qa_utils.RunInstanceCheck(instance, False)
464
  AssertCommand(cmd, fail=True)
465
  AssertCommand(["gnt-instance", "migrate", "--force", "--allow-failover",
466
                 instance.name])
467
  AssertCommand(["gnt-instance", "start", instance.name])
468
  AssertCommand(cmd)
469
  # @InstanceCheck enforces the check that the instance is running
470
  qa_utils.RunInstanceCheck(instance, True)
471

    
472
  AssertCommand(["gnt-instance", "modify", "-B",
473
                 ("%s=%s" %
474
                  (constants.BE_ALWAYS_FAILOVER, constants.VALUE_TRUE)),
475
                 instance.name])
476

    
477
  AssertCommand(cmd)
478
  qa_utils.RunInstanceCheck(instance, True)
479
  # TODO: Verify that a failover has been done instead of a migration
480

    
481
  # TODO: Verify whether the default value is restored here (not hardcoded)
482
  AssertCommand(["gnt-instance", "modify", "-B",
483
                 ("%s=%s" %
484
                  (constants.BE_ALWAYS_FAILOVER, constants.VALUE_FALSE)),
485
                 instance.name])
486

    
487
  AssertCommand(cmd)
488
  qa_utils.RunInstanceCheck(instance, True)
489

    
490

    
491
def TestInstanceInfo(instance):
492
  """gnt-instance info"""
493
  AssertCommand(["gnt-instance", "info", instance.name])
494

    
495

    
496
@InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
497
def TestInstanceModify(instance):
498
  """gnt-instance modify"""
499
  default_hv = qa_config.GetDefaultHypervisor()
500

    
501
  # Assume /sbin/init exists on all systems
502
  test_kernel = "/sbin/init"
503
  test_initrd = test_kernel
504

    
505
  orig_maxmem = qa_config.get(constants.BE_MAXMEM)
506
  orig_minmem = qa_config.get(constants.BE_MINMEM)
507
  #orig_bridge = qa_config.get("bridge", "xen-br0")
508

    
509
  args = [
510
    ["-B", "%s=128" % constants.BE_MINMEM],
511
    ["-B", "%s=128" % constants.BE_MAXMEM],
512
    ["-B", "%s=%s,%s=%s" % (constants.BE_MINMEM, orig_minmem,
513
                            constants.BE_MAXMEM, orig_maxmem)],
514
    ["-B", "%s=2" % constants.BE_VCPUS],
515
    ["-B", "%s=1" % constants.BE_VCPUS],
516
    ["-B", "%s=%s" % (constants.BE_VCPUS, constants.VALUE_DEFAULT)],
517
    ["-B", "%s=%s" % (constants.BE_ALWAYS_FAILOVER, constants.VALUE_TRUE)],
518
    ["-B", "%s=%s" % (constants.BE_ALWAYS_FAILOVER, constants.VALUE_DEFAULT)],
519

    
520
    ["-H", "%s=%s" % (constants.HV_KERNEL_PATH, test_kernel)],
521
    ["-H", "%s=%s" % (constants.HV_KERNEL_PATH, constants.VALUE_DEFAULT)],
522

    
523
    # TODO: bridge tests
524
    #["--bridge", "xen-br1"],
525
    #["--bridge", orig_bridge],
526
    ]
527

    
528
  if default_hv == constants.HT_XEN_PVM:
529
    args.extend([
530
      ["-H", "%s=%s" % (constants.HV_INITRD_PATH, test_initrd)],
531
      ["-H", "no_%s" % (constants.HV_INITRD_PATH, )],
532
      ["-H", "%s=%s" % (constants.HV_INITRD_PATH, constants.VALUE_DEFAULT)],
533
      ])
534
  elif default_hv == constants.HT_XEN_HVM:
535
    args.extend([
536
      ["-H", "%s=acn" % constants.HV_BOOT_ORDER],
537
      ["-H", "%s=%s" % (constants.HV_BOOT_ORDER, constants.VALUE_DEFAULT)],
538
      ])
539
  elif default_hv == constants.HT_KVM and \
540
    qa_config.TestEnabled("instance-device-hotplug"):
541
    args.extend([
542
      ["--net", "-1:add", "--hotplug"],
543
      ["--net", "-1:modify,mac=aa:bb:cc:dd:ee:ff", "--hotplug", "--force"],
544
      ["--net", "-1:remove", "--hotplug"],
545
      ["--disk", "-1:add,size=1G", "--hotplug"],
546
      ["--disk", "-1:remove", "--hotplug"],
547
      ])
548

    
549
  for alist in args:
550
    AssertCommand(["gnt-instance", "modify"] + alist + [instance.name])
551

    
552
  # check no-modify
553
  AssertCommand(["gnt-instance", "modify", instance.name], fail=True)
554

    
555
  # Marking offline while instance is running must fail...
556
  AssertCommand(["gnt-instance", "modify", "--offline", instance.name],
557
                 fail=True)
558

    
559
  # ...while making it online is ok, and should work
560
  AssertCommand(["gnt-instance", "modify", "--online", instance.name])
561

    
562

    
563
@InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
564
def TestInstanceModifyPrimaryAndBack(instance, currentnode, othernode):
565
  """gnt-instance modify --new-primary
566

567
  This will leave the instance on its original primary node, not other node.
568

569
  """
570
  if instance.disk_template != constants.DT_FILE:
571
    print qa_utils.FormatInfo("Test only supported for the file disk template")
572
    return
573

    
574
  cluster_name = qa_config.get("name")
575

    
576
  name = instance.name
577
  current = currentnode.primary
578
  other = othernode.primary
579

    
580
  filestorage = qa_config.get("file-storage-dir",
581
                              pathutils.DEFAULT_FILE_STORAGE_DIR)
582
  disk = os.path.join(filestorage, name)
583

    
584
  AssertCommand(["gnt-instance", "modify", "--new-primary=%s" % other, name],
585
                fail=True)
586
  AssertCommand(["gnt-instance", "shutdown", name])
587
  AssertCommand(["scp", "-oGlobalKnownHostsFile=%s" %
588
                 pathutils.SSH_KNOWN_HOSTS_FILE,
589
                 "-oCheckHostIp=no", "-oStrictHostKeyChecking=yes",
590
                 "-oHashKnownHosts=no", "-oHostKeyAlias=%s" % cluster_name,
591
                 "-r", disk, "%s:%s" % (other, filestorage)], node=current)
592
  AssertCommand(["gnt-instance", "modify", "--new-primary=%s" % other, name])
593
  AssertCommand(["gnt-instance", "startup", name])
594

    
595
  # and back
596
  AssertCommand(["gnt-instance", "shutdown", name])
597
  AssertCommand(["rm", "-rf", disk], node=other)
598
  AssertCommand(["gnt-instance", "modify", "--new-primary=%s" % current, name])
599
  AssertCommand(["gnt-instance", "startup", name])
600

    
601

    
602
@InstanceCheck(INST_DOWN, INST_DOWN, FIRST_ARG)
603
def TestInstanceStoppedModify(instance):
604
  """gnt-instance modify (stopped instance)"""
605
  name = instance.name
606

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

    
610
  # Mark instance as offline
611
  AssertCommand(["gnt-instance", "modify", "--offline", name])
612

    
613
  # When the instance is offline shutdown should only work with --force,
614
  # while start should never work
615
  AssertCommand(["gnt-instance", "shutdown", name], fail=True)
616
  AssertCommand(["gnt-instance", "shutdown", "--force", name])
617
  AssertCommand(["gnt-instance", "start", name], fail=True)
618
  AssertCommand(["gnt-instance", "start", "--force", name], fail=True)
619

    
620
  # Also do offline to offline
621
  AssertCommand(["gnt-instance", "modify", "--offline", name])
622

    
623
  # And online again
624
  AssertCommand(["gnt-instance", "modify", "--online", name])
625

    
626

    
627
@InstanceCheck(INST_DOWN, INST_DOWN, FIRST_ARG)
628
def TestInstanceConvertDiskToPlain(instance, inodes):
629
  """gnt-instance modify -t"""
630
  name = instance.name
631

    
632
  template = instance.disk_template
633
  if template != constants.DT_DRBD8:
634
    print qa_utils.FormatInfo("Unsupported template %s, skipping conversion"
635
                              " test" % template)
636
    return
637

    
638
  assert len(inodes) == 2
639
  AssertCommand(["gnt-instance", "modify", "-t", constants.DT_PLAIN, name])
640
  AssertCommand(["gnt-instance", "modify", "-t", constants.DT_DRBD8,
641
                 "-n", inodes[1].primary, name])
642

    
643

    
644
@InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
645
def TestInstanceModifyDisks(instance):
646
  """gnt-instance modify --disk"""
647
  if not IsDiskSupported(instance):
648
    print qa_utils.FormatInfo("Instance doesn't support disks, skipping test")
649
    return
650

    
651
  disk_conf = qa_config.GetDiskOptions()[-1]
652
  size = disk_conf.get("size")
653
  name = instance.name
654
  build_cmd = lambda arg: ["gnt-instance", "modify", "--disk", arg, name]
655
  if qa_config.AreSpindlesSupported():
656
    spindles = disk_conf.get("spindles")
657
    spindles_supported = True
658
  else:
659
    # Any number is good for spindles in this case
660
    spindles = 1
661
    spindles_supported = False
662
  AssertCommand(build_cmd("add:size=%s,spindles=%s" % (size, spindles)),
663
                fail=not spindles_supported)
664
  AssertCommand(build_cmd("add:size=%s" % size),
665
                fail=spindles_supported)
666
  # Exactly one of the above commands has succeded, so we need one remove
667
  AssertCommand(build_cmd("remove"))
668

    
669

    
670
@InstanceCheck(INST_DOWN, INST_DOWN, FIRST_ARG)
671
def TestInstanceGrowDisk(instance):
672
  """gnt-instance grow-disk"""
673
  if instance.disk_template == constants.DT_DISKLESS:
674
    print qa_utils.FormatInfo("Test not supported for diskless instances")
675
    return
676

    
677
  name = instance.name
678
  disks = qa_config.GetDiskOptions()
679
  all_size = [d.get("size") for d in disks]
680
  all_grow = [d.get("growth") for d in disks]
681

    
682
  if not all_grow:
683
    # missing disk sizes but instance grow disk has been enabled,
684
    # let's set fixed/nomimal growth
685
    all_grow = ["128M" for _ in all_size]
686

    
687
  for idx, (size, grow) in enumerate(zip(all_size, all_grow)):
688
    # succeed in grow by amount
689
    AssertCommand(["gnt-instance", "grow-disk", name, str(idx), grow])
690
    # fail in grow to the old size
691
    AssertCommand(["gnt-instance", "grow-disk", "--absolute", name, str(idx),
692
                   size], fail=True)
693
    # succeed to grow to old size + 2 * growth
694
    int_size = utils.ParseUnit(size)
695
    int_grow = utils.ParseUnit(grow)
696
    AssertCommand(["gnt-instance", "grow-disk", "--absolute", name, str(idx),
697
                   str(int_size + 2 * int_grow)])
698

    
699

    
700
@InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
701
def TestInstanceDeviceNames(instance):
702
  if instance.disk_template == constants.DT_DISKLESS:
703
    print qa_utils.FormatInfo("Test not supported for diskless instances")
704
    return
705

    
706
  name = instance.name
707
  for dev_type in ["disk", "net"]:
708
    if dev_type == "disk":
709
      options = ",size=512M"
710
      if qa_config.AreSpindlesSupported():
711
        options += ",spindles=1"
712
    else:
713
      options = ""
714
    # succeed in adding a device named 'test_device'
715
    AssertCommand(["gnt-instance", "modify",
716
                   "--%s=-1:add,name=test_device%s" % (dev_type, options),
717
                   name])
718
    # succeed in removing the 'test_device'
719
    AssertCommand(["gnt-instance", "modify",
720
                   "--%s=test_device:remove" % dev_type,
721
                   name])
722
    # fail to add two devices with the same name
723
    AssertCommand(["gnt-instance", "modify",
724
                   "--%s=-1:add,name=test_device%s" % (dev_type, options),
725
                   "--%s=-1:add,name=test_device%s" % (dev_type, options),
726
                   name], fail=True)
727
    # fail to add a device with invalid name
728
    AssertCommand(["gnt-instance", "modify",
729
                   "--%s=-1:add,name=2%s" % (dev_type, options),
730
                   name], fail=True)
731
  # Rename disks
732
  disks = qa_config.GetDiskOptions()
733
  disk_names = [d.get("name") for d in disks]
734
  for idx, disk_name in enumerate(disk_names):
735
    # Refer to disk by idx
736
    AssertCommand(["gnt-instance", "modify",
737
                   "--disk=%s:modify,name=renamed" % idx,
738
                   name])
739
    # Refer to by name and rename to original name
740
    AssertCommand(["gnt-instance", "modify",
741
                   "--disk=renamed:modify,name=%s" % disk_name,
742
                   name])
743
  if len(disks) >= 2:
744
    # fail in renaming to disks to the same name
745
    AssertCommand(["gnt-instance", "modify",
746
                   "--disk=0:modify,name=same_name",
747
                   "--disk=1:modify,name=same_name",
748
                   name], fail=True)
749

    
750

    
751
def TestInstanceList():
752
  """gnt-instance list"""
753
  qa_utils.GenericQueryTest("gnt-instance", query.INSTANCE_FIELDS.keys())
754

    
755

    
756
def TestInstanceListFields():
757
  """gnt-instance list-fields"""
758
  qa_utils.GenericQueryFieldsTest("gnt-instance", query.INSTANCE_FIELDS.keys())
759

    
760

    
761
@InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
762
def TestInstanceConsole(instance):
763
  """gnt-instance console"""
764
  AssertCommand(["gnt-instance", "console", "--show-cmd", instance.name])
765

    
766

    
767
@InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
768
def TestReplaceDisks(instance, curr_nodes, other_nodes):
769
  """gnt-instance replace-disks"""
770
  def buildcmd(args):
771
    cmd = ["gnt-instance", "replace-disks"]
772
    cmd.extend(args)
773
    cmd.append(instance.name)
774
    return cmd
775

    
776
  if not IsDiskReplacingSupported(instance):
777
    print qa_utils.FormatInfo("Instance doesn't support disk replacing,"
778
                              " skipping test")
779
    return
780

    
781
  # Currently all supported templates have one primary and one secondary node
782
  assert len(curr_nodes) == 2
783
  snode = curr_nodes[1]
784
  assert len(other_nodes) == 1
785
  othernode = other_nodes[0]
786

    
787
  options = qa_config.get("options", {})
788
  use_ialloc = options.get("use-iallocators", True)
789
  for data in [
790
    ["-p"],
791
    ["-s"],
792
    # A placeholder; the actual command choice depends on use_ialloc
793
    None,
794
    # Restore the original secondary
795
    ["--new-secondary=%s" % snode.primary],
796
    ]:
797
    if data is None:
798
      if use_ialloc:
799
        data = ["-I", constants.DEFAULT_IALLOCATOR_SHORTCUT]
800
      else:
801
        data = ["--new-secondary=%s" % othernode.primary]
802
    AssertCommand(buildcmd(data))
803

    
804
  AssertCommand(buildcmd(["-a"]))
805
  AssertCommand(["gnt-instance", "stop", instance.name])
806
  AssertCommand(buildcmd(["-a"]), fail=True)
807
  AssertCommand(["gnt-instance", "activate-disks", instance.name])
808
  AssertCommand(["gnt-instance", "activate-disks", "--wait-for-sync",
809
                 instance.name])
810
  AssertCommand(buildcmd(["-a"]))
811
  AssertCommand(["gnt-instance", "start", instance.name])
812

    
813

    
814
def _AssertRecreateDisks(cmdargs, instance, fail=False, check=True,
815
                         destroy=True):
816
  """Execute gnt-instance recreate-disks and check the result
817

818
  @param cmdargs: Arguments (instance name excluded)
819
  @param instance: Instance to operate on
820
  @param fail: True if the command is expected to fail
821
  @param check: If True and fail is False, check that the disks work
822
  @prama destroy: If True, destroy the old disks first
823

824
  """
825
  if destroy:
826
    _DestroyInstanceDisks(instance)
827
  AssertCommand((["gnt-instance", "recreate-disks"] + cmdargs +
828
                 [instance.name]), fail)
829
  if not fail and check:
830
    # Quick check that the disks are there
831
    AssertCommand(["gnt-instance", "activate-disks", instance.name])
832
    AssertCommand(["gnt-instance", "activate-disks", "--wait-for-sync",
833
                   instance.name])
834
    AssertCommand(["gnt-instance", "deactivate-disks", instance.name])
835

    
836

    
837
def _BuildRecreateDisksOpts(en_disks, with_spindles, with_growth,
838
                            spindles_supported):
839
  if with_spindles:
840
    if spindles_supported:
841
      if with_growth:
842
        build_spindles_opt = (lambda disk:
843
                              ",spindles=%s" %
844
                              (disk["spindles"] + disk["spindles-growth"]))
845
      else:
846
        build_spindles_opt = (lambda disk:
847
                              ",spindles=%s" % disk["spindles"])
848
    else:
849
      build_spindles_opt = (lambda _: ",spindles=1")
850
  else:
851
    build_spindles_opt = (lambda _: "")
852
  if with_growth:
853
    build_size_opt = (lambda disk:
854
                      "size=%s" % (utils.ParseUnit(disk["size"]) +
855
                                   utils.ParseUnit(disk["growth"])))
856
  else:
857
    build_size_opt = (lambda disk: "size=%s" % disk["size"])
858
  build_disk_opt = (lambda (idx, disk):
859
                    "--disk=%s:%s%s" % (idx, build_size_opt(disk),
860
                                        build_spindles_opt(disk)))
861
  return map(build_disk_opt, en_disks)
862

    
863

    
864
@InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
865
def TestRecreateDisks(instance, inodes, othernodes):
866
  """gnt-instance recreate-disks
867

868
  @param instance: Instance to work on
869
  @param inodes: List of the current nodes of the instance
870
  @param othernodes: list/tuple of nodes where to temporarily recreate disks
871

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

    
932

    
933
@InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
934
def TestInstanceExport(instance, node):
935
  """gnt-backup export -n ..."""
936
  name = instance.name
937
  # Export does not work for file-based templates, thus we skip the test
938
  if instance.disk_template in [constants.DT_FILE, constants.DT_SHARED_FILE]:
939
    return
940
  AssertCommand(["gnt-backup", "export", "-n", node.primary, name])
941
  return qa_utils.ResolveInstanceName(name)
942

    
943

    
944
@InstanceCheck(None, INST_DOWN, FIRST_ARG)
945
def TestInstanceExportWithRemove(instance, node):
946
  """gnt-backup export --remove-instance"""
947
  AssertCommand(["gnt-backup", "export", "-n", node.primary,
948
                 "--remove-instance", instance.name])
949

    
950

    
951
@InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
952
def TestInstanceExportNoTarget(instance):
953
  """gnt-backup export (without target node, should fail)"""
954
  AssertCommand(["gnt-backup", "export", instance.name], fail=True)
955

    
956

    
957
@InstanceCheck(None, INST_DOWN, FIRST_ARG)
958
def TestInstanceImport(newinst, node, expnode, name):
959
  """gnt-backup import"""
960
  templ = constants.DT_PLAIN
961
  if not qa_config.IsTemplateSupported(templ):
962
    return
963
  cmd = (["gnt-backup", "import",
964
          "--disk-template=%s" % templ,
965
          "--no-ip-check",
966
          "--src-node=%s" % expnode.primary,
967
          "--src-dir=%s/%s" % (pathutils.EXPORT_DIR, name),
968
          "--node=%s" % node.primary] +
969
         GetGenericAddParameters(newinst, templ,
970
                                  force_mac=constants.VALUE_GENERATE))
971
  cmd.append(newinst.name)
972
  AssertCommand(cmd)
973
  newinst.SetDiskTemplate(templ)
974

    
975

    
976
def TestBackupList(expnode):
977
  """gnt-backup list"""
978
  AssertCommand(["gnt-backup", "list", "--node=%s" % expnode.primary])
979

    
980
  qa_utils.GenericQueryTest("gnt-backup", query.EXPORT_FIELDS.keys(),
981
                            namefield=None, test_unknown=False)
982

    
983

    
984
def TestBackupListFields():
985
  """gnt-backup list-fields"""
986
  qa_utils.GenericQueryFieldsTest("gnt-backup", query.EXPORT_FIELDS.keys())
987

    
988

    
989
def TestRemoveInstanceOfflineNode(instance, snode, set_offline, set_online):
990
  """gnt-instance remove with an off-line node
991

992
  @param instance: instance
993
  @param snode: secondary node, to be set offline
994
  @param set_offline: function to call to set the node off-line
995
  @param set_online: function to call to set the node on-line
996

997
  """
998
  info = GetInstanceInfo(instance.name)
999
  set_offline(snode)
1000
  try:
1001
    TestInstanceRemove(instance)
1002
  finally:
1003
    set_online(snode)
1004

    
1005
  # Clean up the disks on the offline node, if necessary
1006
  if instance.disk_template not in constants.DTS_EXT_MIRROR:
1007
    # FIXME: abstract the cleanup inside the disks
1008
    if info["storage-type"] == constants.ST_LVM_VG:
1009
      for minor in info["drbd-minors"][snode.primary]:
1010
        # DRBD 8.3 syntax comes first, then DRBD 8.4 syntax. The 8.4 syntax
1011
        # relies on the fact that we always create a resources for each minor,
1012
        # and that this resources is always named resource{minor}.
1013
        # As 'drbdsetup 0 down' does return success (even though that's invalid
1014
        # syntax), we always have to perform both commands and ignore the
1015
        # output.
1016
        drbd_shutdown_cmd = \
1017
          "(drbdsetup %d down >/dev/null 2>&1;" \
1018
          " drbdsetup down resource%d >/dev/null 2>&1) || /bin/true" % \
1019
            (minor, minor)
1020
        AssertCommand(drbd_shutdown_cmd, node=snode)
1021
      AssertCommand(["lvremove", "-f"] + info["volumes"], node=snode)
1022
    elif info["storage-type"] == constants.ST_FILE:
1023
      filestorage = qa_config.get("file-storage-dir",
1024
                                  pathutils.DEFAULT_FILE_STORAGE_DIR)
1025
      disk = os.path.join(filestorage, instance.name)
1026
      AssertCommand(["rm", "-rf", disk], node=snode)
1027

    
1028

    
1029
def TestInstanceCreationRestrictedByDiskTemplates():
1030
  """Test adding instances for disabled disk templates."""
1031
  if qa_config.TestEnabled("cluster-exclusive-storage"):
1032
    # These tests are valid only for non-exclusive storage
1033
    return
1034

    
1035
  enabled_disk_templates = qa_config.GetEnabledDiskTemplates()
1036
  nodes = qa_config.AcquireManyNodes(2)
1037

    
1038
  # Setup the cluster with the enabled_disk_templates
1039
  AssertCommand(
1040
    ["gnt-cluster", "modify",
1041
     "--enabled-disk-templates=%s" % ",".join(enabled_disk_templates),
1042
     "--ipolicy-disk-templates=%s" % ",".join(enabled_disk_templates)],
1043
    fail=False)
1044

    
1045
  # Test instance creation for enabled disk templates
1046
  for disk_template in enabled_disk_templates:
1047
    instance = CreateInstanceByDiskTemplate(nodes, disk_template, fail=False)
1048
    TestInstanceRemove(instance)
1049
    instance.Release()
1050

    
1051
  # Test that instance creation fails for disabled disk templates
1052
  disabled_disk_templates = list(constants.DISK_TEMPLATES
1053
                                 - set(enabled_disk_templates))
1054
  for disk_template in disabled_disk_templates:
1055
    instance = CreateInstanceByDiskTemplate(nodes, disk_template, fail=True)
1056

    
1057
  # Test instance creation for after disabling enabled disk templates
1058
  if (len(enabled_disk_templates) > 1):
1059
    # Partition the disk templates, enable them separately and check if the
1060
    # disabled ones cannot be used by instances.
1061
    middle = len(enabled_disk_templates) / 2
1062
    templates1 = enabled_disk_templates[:middle]
1063
    templates2 = enabled_disk_templates[middle:]
1064

    
1065
    for (enabled, disabled) in [(templates1, templates2),
1066
                                (templates2, templates1)]:
1067
      AssertCommand(["gnt-cluster", "modify",
1068
                     "--enabled-disk-templates=%s" % ",".join(enabled),
1069
                     "--ipolicy-disk-templates=%s" % ",".join(enabled)],
1070
                    fail=False)
1071
      for disk_template in disabled:
1072
        CreateInstanceByDiskTemplate(nodes, disk_template, fail=True)
1073
  elif (len(enabled_disk_templates) == 1):
1074
    # If only one disk template is enabled in the QA config, we have to enable
1075
    # some other templates in order to test if the disabling the only enabled
1076
    # disk template prohibits creating instances of that template.
1077
    other_disk_templates = list(
1078
                             set([constants.DT_DISKLESS, constants.DT_BLOCK]) -
1079
                             set(enabled_disk_templates))
1080
    AssertCommand(["gnt-cluster", "modify",
1081
                   "--enabled-disk-templates=%s" %
1082
                     ",".join(other_disk_templates),
1083
                   "--ipolicy-disk-templates=%s" %
1084
                     ",".join(other_disk_templates)],
1085
                  fail=False)
1086
    CreateInstanceByDiskTemplate(nodes, enabled_disk_templates[0], fail=True)
1087
  else:
1088
    raise qa_error.Error("Please enable at least one disk template"
1089
                         " in your QA setup.")
1090

    
1091
  # Restore initially enabled disk templates
1092
  AssertCommand(["gnt-cluster", "modify",
1093
                 "--enabled-disk-templates=%s" %
1094
                   ",".join(enabled_disk_templates),
1095
                 "--ipolicy-disk-templates=%s" %
1096
                   ",".join(enabled_disk_templates)],
1097
                 fail=False)
1098

    
1099

    
1100
@InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
1101
def _TestInstanceUserDown(instance, master, hv_shutdown_fn):
1102
  # Shutdown instance and bring instance status to 'USER_down'
1103
  hv_shutdown_fn()
1104

    
1105
  cmd = ["gnt-instance", "list", "--no-headers", "-o", "status", instance.name]
1106
  result_output = qa_utils.GetCommandOutput(master.primary,
1107
                                            utils.ShellQuoteArgs(cmd))
1108
  AssertEqual(result_output.strip(), constants.INSTST_USERDOWN)
1109

    
1110
  # Fail to bring instance status to 'running'
1111
  AssertCommand(["gnt-instance", "start", instance.name], fail=True)
1112

    
1113
  cmd = ["gnt-instance", "list", "--no-headers", "-o", "status", instance.name]
1114
  result_output = qa_utils.GetCommandOutput(master.primary,
1115
                                            utils.ShellQuoteArgs(cmd))
1116
  AssertEqual(result_output.strip(), constants.INSTST_USERDOWN)
1117

    
1118
  # Bring instance status to 'ADMIN_down'
1119
  AssertCommand(["gnt-instance", "shutdown", instance.name])
1120

    
1121
  cmd = ["gnt-instance", "list", "--no-headers", "-o", "status", instance.name]
1122
  result_output = qa_utils.GetCommandOutput(master.primary,
1123
                                            utils.ShellQuoteArgs(cmd))
1124
  AssertEqual(result_output.strip(), constants.INSTST_ADMINDOWN)
1125

    
1126
  # Bring instance status to 'running'
1127
  AssertCommand(["gnt-instance", "start", instance.name])
1128

    
1129
  cmd = ["gnt-instance", "list", "--no-headers", "-o", "status", instance.name]
1130
  result_output = qa_utils.GetCommandOutput(master.primary,
1131
                                            utils.ShellQuoteArgs(cmd))
1132
  AssertEqual(result_output.strip(), constants.INSTST_RUNNING)
1133

    
1134
  # Bring instance status to 'ADMIN_down' forcibly
1135
  AssertCommand(["gnt-instance", "shutdown", "-f", instance.name])
1136

    
1137
  cmd = ["gnt-instance", "list", "--no-headers", "-o", "status", instance.name]
1138
  result_output = qa_utils.GetCommandOutput(master.primary,
1139
                                            utils.ShellQuoteArgs(cmd))
1140
  AssertEqual(result_output.strip(), constants.INSTST_ADMINDOWN)
1141

    
1142
  # Bring instance status to 'running'
1143
  AssertCommand(["gnt-instance", "start", instance.name])
1144

    
1145
  cmd = ["gnt-instance", "list", "--no-headers", "-o", "status", instance.name]
1146
  result_output = qa_utils.GetCommandOutput(master.primary,
1147
                                            utils.ShellQuoteArgs(cmd))
1148
  AssertEqual(result_output.strip(), constants.INSTST_RUNNING)
1149

    
1150

    
1151
@InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
1152
def _TestInstanceUserDownXen(instance, master):
1153
  primary = _GetInstanceField(instance.name, "pnode")
1154
  fn = lambda: AssertCommand(["xm", "shutdown", "-w", instance.name],
1155
                             node=primary)
1156
  _TestInstanceUserDown(instance, master, fn)
1157

    
1158

    
1159
@InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
1160
def _TestInstanceUserDownKvm(instance, master):
1161
  def _StopKVMInstance():
1162
    AssertCommand("pkill -f \"\\-name %s\"" % instance.name, node=primary)
1163
    time.sleep(10)
1164

    
1165
  AssertCommand(["gnt-instance", "modify", "-H", "user_shutdown=true",
1166
                 instance.name])
1167

    
1168
  # The instance needs to reboot not because the 'user_shutdown'
1169
  # parameter was modified but because the KVM daemon need to be
1170
  # started, given that the instance was first created with user
1171
  # shutdown disabled.
1172
  AssertCommand(["gnt-instance", "reboot", instance.name])
1173

    
1174
  primary = _GetInstanceField(instance.name, "pnode")
1175
  _TestInstanceUserDown(instance, master, _StopKVMInstance)
1176

    
1177

    
1178
def TestInstanceUserDown(instance, master):
1179
  """Tests user shutdown"""
1180
  enabled_hypervisors = qa_config.GetEnabledHypervisors()
1181

    
1182
  for (hv, fn) in [(constants.HT_XEN_PVM, _TestInstanceUserDownXen),
1183
                   (constants.HT_XEN_HVM, _TestInstanceUserDownXen),
1184
                   (constants.HT_KVM, _TestInstanceUserDownKvm)]:
1185
    if hv in enabled_hypervisors:
1186
      qa_daemon.TestPauseWatcher()
1187
      fn(instance, master)
1188
      qa_daemon.TestResumeWatcher()
1189
    else:
1190
      print "%s hypervisor is not enabled, skipping test for this hypervisor" \
1191
          % hv
1192

    
1193

    
1194
available_instance_tests = [
1195
  ("instance-add-plain-disk", constants.DT_PLAIN,
1196
   TestInstanceAddWithPlainDisk, 1),
1197
  ("instance-add-drbd-disk", constants.DT_DRBD8,
1198
   TestInstanceAddWithDrbdDisk, 2),
1199
  ("instance-add-diskless", constants.DT_DISKLESS,
1200
   TestInstanceAddDiskless, 1),
1201
  ("instance-add-file", constants.DT_FILE,
1202
   TestInstanceAddFile, 1),
1203
  ("instance-add-shared-file", constants.DT_SHARED_FILE,
1204
   TestInstanceAddSharedFile, 1),
1205
  ]