Statistics
| Branch: | Tag: | Revision:

root / qa / qa_instance.py @ fcc27323

History | View | Annotate | Download (43.8 kB)

1
#
2
#
3

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

    
21

    
22
"""Instance related QA tests.
23

24
"""
25

    
26
import 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_utils
37
import qa_error
38

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

    
47

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

    
51

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

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

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

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

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

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

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

    
123

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

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

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

    
149

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

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

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

    
165

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

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

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

    
185

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

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

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

    
207

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

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

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

    
238

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

    
242

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

    
246

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

    
250

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

    
254

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

    
264

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

    
271

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

    
279

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

    
287

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

    
295

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

    
301

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

    
307

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

    
313

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

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

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

    
333

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

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

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

    
349

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

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

357
  """
358
  CheckSsconfInstanceList(rename_source)
359

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

    
369
  info = GetInstanceInfo(rename_source)
370

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

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

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

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

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

    
411

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

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

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

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

    
429

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

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

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

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

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

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

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

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

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

    
489

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

    
494

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

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

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

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

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

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

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

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

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

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

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

    
561

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

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

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

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

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

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

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

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

    
600

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

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

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

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

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

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

    
625

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

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

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

    
642

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

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

    
668

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

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

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

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

    
698

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

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

    
749

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

    
754

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

    
759

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

    
765

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

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

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

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

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

    
812

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

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

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

    
835

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

    
862

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

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

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

    
931

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

    
942

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

    
949

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

    
955

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

    
974

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

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

    
982

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

    
987

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

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

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

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

    
1027

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

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

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

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

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

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

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

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

    
1098

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

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

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

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

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

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

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

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

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

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

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

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

    
1149

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

    
1157

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

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

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

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

    
1176

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

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

    
1190

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