Statistics
| Branch: | Tag: | Revision:

root / qa / qa_instance.py @ d7185512

History | View | Annotate | Download (40.1 kB)

1
#
2
#
3

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

    
21

    
22
"""Instance related QA tests.
23

24
"""
25

    
26
import operator
27
import os
28
import re
29

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

    
35
import qa_config
36
import qa_utils
37
import qa_error
38

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

    
42

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

    
46

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

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

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

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

    
72
  return params
73

    
74

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

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

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

    
97
    AssertCommand(cmd, fail=fail)
98

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

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

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

    
113

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

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

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

    
131

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

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

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

    
145

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

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

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

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

    
178

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

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

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

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

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

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

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

    
250

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

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

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

    
276

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

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

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

    
292

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

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

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

    
312

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

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

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

    
334

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

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

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

    
365

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

    
369

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

    
373

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

    
377

    
378
def IsDiskSupported(instance):
379
  return instance.disk_template != constants.DT_DISKLESS
380

    
381

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

    
391

    
392
@InstanceCheck(None, INST_UP, RETURN_VALUE)
393
def TestInstanceAddWithDrbdDisk(nodes):
394
  """gnt-instance add -t drbd"""
395
  if constants.DT_DRBD8 in qa_config.GetEnabledDiskTemplates():
396
    return _CreateInstanceDrbd8(nodes)
397

    
398

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

    
406

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

    
414

    
415
@InstanceCheck(None, INST_DOWN, FIRST_ARG)
416
def TestInstanceRemove(instance):
417
  """gnt-instance remove"""
418
  AssertCommand(["gnt-instance", "remove", "-f", instance.name])
419

    
420

    
421
@InstanceCheck(INST_DOWN, INST_UP, FIRST_ARG)
422
def TestInstanceStartup(instance):
423
  """gnt-instance startup"""
424
  AssertCommand(["gnt-instance", "startup", instance.name])
425

    
426

    
427
@InstanceCheck(INST_UP, INST_DOWN, FIRST_ARG)
428
def TestInstanceShutdown(instance):
429
  """gnt-instance shutdown"""
430
  AssertCommand(["gnt-instance", "shutdown", instance.name])
431

    
432

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

    
442
  AssertCommand(["gnt-instance", "shutdown", name])
443
  qa_utils.RunInstanceCheck(instance, False)
444
  AssertCommand(["gnt-instance", "reboot", name])
445

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

    
452

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

    
460
  AssertCommand(["gnt-instance", "reinstall", "-f", instance.name])
461

    
462
  # Test with non-existant OS definition
463
  AssertCommand(["gnt-instance", "reinstall", "-f",
464
                 "--os-type=NonExistantOsForQa",
465
                 instance.name],
466
                fail=True)
467

    
468

    
469
def _ReadSsconfInstanceList():
470
  """Reads ssconf_instance_list from the master node.
471

472
  """
473
  master = qa_config.GetMasterNode()
474

    
475
  ssconf_path = utils.PathJoin(pathutils.DATA_DIR,
476
                               "ssconf_%s" % constants.SS_INSTANCE_LIST)
477

    
478
  cmd = ["cat", qa_utils.MakeNodePath(master, ssconf_path)]
479

    
480
  return qa_utils.GetCommandOutput(master.primary,
481
                                   utils.ShellQuoteArgs(cmd)).splitlines()
482

    
483

    
484
def _CheckSsconfInstanceList(instance):
485
  """Checks if a certain instance is in the ssconf instance list.
486

487
  @type instance: string
488
  @param instance: Instance name
489

490
  """
491
  AssertIn(qa_utils.ResolveInstanceName(instance),
492
           _ReadSsconfInstanceList())
493

    
494

    
495
@InstanceCheck(INST_DOWN, INST_DOWN, FIRST_ARG)
496
def TestInstanceRenameAndBack(rename_source, rename_target):
497
  """gnt-instance rename
498

499
  This must leave the instance with the original name, not the target
500
  name.
501

502
  """
503
  _CheckSsconfInstanceList(rename_source)
504

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

    
514
  info = _GetInstanceInfo(rename_source)
515

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

    
531
  # and now rename instance to rename_target...
532
  AssertCommand(["gnt-instance", "rename", rename_source, rename_target])
533
  _CheckSsconfInstanceList(rename_target)
534
  qa_utils.RunInstanceCheck(rename_source, False)
535
  qa_utils.RunInstanceCheck(rename_target, False)
536

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

    
545
  # and back
546
  AssertCommand(["gnt-instance", "rename", rename_target, rename_source])
547
  _CheckSsconfInstanceList(rename_source)
548
  qa_utils.RunInstanceCheck(rename_target, False)
549

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

    
556

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

    
565
  cmd = ["gnt-instance", "failover", "--force", instance.name]
566

    
567
  # failover ...
568
  AssertCommand(cmd)
569
  qa_utils.RunInstanceCheck(instance, True)
570

    
571
  # ... and back
572
  AssertCommand(cmd)
573

    
574

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

    
583
  cmd = ["gnt-instance", "migrate", "--force", instance.name]
584
  af_par = constants.BE_ALWAYS_FAILOVER
585
  af_field = "be/" + constants.BE_ALWAYS_FAILOVER
586
  af_init_val = _GetBoolInstanceField(instance.name, af_field)
587

    
588
  # migrate ...
589
  AssertCommand(cmd)
590
  # TODO: Verify the choice between failover and migration
591
  qa_utils.RunInstanceCheck(instance, True)
592

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

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

    
616
  AssertCommand(["gnt-instance", "modify", "-B",
617
                 ("%s=%s" %
618
                  (constants.BE_ALWAYS_FAILOVER, constants.VALUE_TRUE)),
619
                 instance.name])
620

    
621
  AssertCommand(cmd)
622
  qa_utils.RunInstanceCheck(instance, True)
623
  # TODO: Verify that a failover has been done instead of a migration
624

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

    
631
  AssertCommand(cmd)
632
  qa_utils.RunInstanceCheck(instance, True)
633

    
634

    
635
def TestInstanceInfo(instance):
636
  """gnt-instance info"""
637
  AssertCommand(["gnt-instance", "info", instance.name])
638

    
639

    
640
@InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
641
def TestInstanceModify(instance):
642
  """gnt-instance modify"""
643
  default_hv = qa_config.GetDefaultHypervisor()
644

    
645
  # Assume /sbin/init exists on all systems
646
  test_kernel = "/sbin/init"
647
  test_initrd = test_kernel
648

    
649
  orig_maxmem = qa_config.get(constants.BE_MAXMEM)
650
  orig_minmem = qa_config.get(constants.BE_MINMEM)
651
  #orig_bridge = qa_config.get("bridge", "xen-br0")
652

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

    
664
    ["-H", "%s=%s" % (constants.HV_KERNEL_PATH, test_kernel)],
665
    ["-H", "%s=%s" % (constants.HV_KERNEL_PATH, constants.VALUE_DEFAULT)],
666

    
667
    # TODO: bridge tests
668
    #["--bridge", "xen-br1"],
669
    #["--bridge", orig_bridge],
670
    ]
671

    
672
  if default_hv == constants.HT_XEN_PVM:
673
    args.extend([
674
      ["-H", "%s=%s" % (constants.HV_INITRD_PATH, test_initrd)],
675
      ["-H", "no_%s" % (constants.HV_INITRD_PATH, )],
676
      ["-H", "%s=%s" % (constants.HV_INITRD_PATH, constants.VALUE_DEFAULT)],
677
      ])
678
  elif default_hv == constants.HT_XEN_HVM:
679
    args.extend([
680
      ["-H", "%s=acn" % constants.HV_BOOT_ORDER],
681
      ["-H", "%s=%s" % (constants.HV_BOOT_ORDER, constants.VALUE_DEFAULT)],
682
      ])
683
  elif default_hv == constants.HT_KVM and \
684
    qa_config.TestEnabled("instance-device-hotplug"):
685
    args.extend([
686
      ["--net", "-1:add", "--hotplug"],
687
      ["--net", "-1:modify,mac=aa:bb:cc:dd:ee:ff", "--hotplug"],
688
      ["--net", "-1:remove", "--hotplug"],
689
      ["--disk", "-1:add,size=1G", "--hotplug"],
690
      ["--disk", "-1:remove", "--hotplug"],
691
      ])
692

    
693
  for alist in args:
694
    AssertCommand(["gnt-instance", "modify"] + alist + [instance.name])
695

    
696
  # check no-modify
697
  AssertCommand(["gnt-instance", "modify", instance.name], fail=True)
698

    
699
  # Marking offline while instance is running must fail...
700
  AssertCommand(["gnt-instance", "modify", "--offline", instance.name],
701
                 fail=True)
702

    
703
  # ...while making it online is ok, and should work
704
  AssertCommand(["gnt-instance", "modify", "--online", instance.name])
705

    
706

    
707
@InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
708
def TestInstanceModifyPrimaryAndBack(instance, currentnode, othernode):
709
  """gnt-instance modify --new-primary
710

711
  This will leave the instance on its original primary node, not other node.
712

713
  """
714
  if instance.disk_template != constants.DT_FILE:
715
    print qa_utils.FormatInfo("Test only supported for the file disk template")
716
    return
717

    
718
  cluster_name = qa_config.get("name")
719

    
720
  name = instance.name
721
  current = currentnode.primary
722
  other = othernode.primary
723

    
724
  # FIXME: the qa doesn't have a customizable file storage dir parameter. As
725
  # such for now we use the default.
726
  filestorage = pathutils.DEFAULT_FILE_STORAGE_DIR
727
  disk = os.path.join(filestorage, name)
728

    
729
  AssertCommand(["gnt-instance", "modify", "--new-primary=%s" % other, name],
730
                fail=True)
731
  AssertCommand(["gnt-instance", "shutdown", name])
732
  AssertCommand(["scp", "-oGlobalKnownHostsFile=%s" %
733
                 pathutils.SSH_KNOWN_HOSTS_FILE,
734
                 "-oCheckHostIp=no", "-oStrictHostKeyChecking=yes",
735
                 "-oHashKnownHosts=no", "-oHostKeyAlias=%s" % cluster_name,
736
                 "-r", disk, "%s:%s" % (other, filestorage)], node=current)
737
  AssertCommand(["gnt-instance", "modify", "--new-primary=%s" % other, name])
738
  AssertCommand(["gnt-instance", "startup", name])
739

    
740
  # and back
741
  AssertCommand(["gnt-instance", "shutdown", name])
742
  AssertCommand(["rm", "-rf", disk], node=other)
743
  AssertCommand(["gnt-instance", "modify", "--new-primary=%s" % current, name])
744
  AssertCommand(["gnt-instance", "startup", name])
745

    
746

    
747
@InstanceCheck(INST_DOWN, INST_DOWN, FIRST_ARG)
748
def TestInstanceStoppedModify(instance):
749
  """gnt-instance modify (stopped instance)"""
750
  name = instance.name
751

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

    
755
  # Mark instance as offline
756
  AssertCommand(["gnt-instance", "modify", "--offline", name])
757

    
758
  # When the instance is offline shutdown should only work with --force,
759
  # while start should never work
760
  AssertCommand(["gnt-instance", "shutdown", name], fail=True)
761
  AssertCommand(["gnt-instance", "shutdown", "--force", name])
762
  AssertCommand(["gnt-instance", "start", name], fail=True)
763
  AssertCommand(["gnt-instance", "start", "--force", name], fail=True)
764

    
765
  # Also do offline to offline
766
  AssertCommand(["gnt-instance", "modify", "--offline", name])
767

    
768
  # And online again
769
  AssertCommand(["gnt-instance", "modify", "--online", name])
770

    
771

    
772
@InstanceCheck(INST_DOWN, INST_DOWN, FIRST_ARG)
773
def TestInstanceConvertDiskToPlain(instance, inodes):
774
  """gnt-instance modify -t"""
775
  name = instance.name
776

    
777
  template = instance.disk_template
778
  if template != constants.DT_DRBD8:
779
    print qa_utils.FormatInfo("Unsupported template %s, skipping conversion"
780
                              " test" % template)
781
    return
782

    
783
  assert len(inodes) == 2
784
  AssertCommand(["gnt-instance", "modify", "-t", constants.DT_PLAIN, name])
785
  AssertCommand(["gnt-instance", "modify", "-t", constants.DT_DRBD8,
786
                 "-n", inodes[1].primary, name])
787

    
788

    
789
@InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
790
def TestInstanceModifyDisks(instance):
791
  """gnt-instance modify --disk"""
792
  if not IsDiskSupported(instance):
793
    print qa_utils.FormatInfo("Instance doesn't support disks, skipping test")
794
    return
795

    
796
  size = qa_config.GetDiskOptions()[-1].get("size")
797
  name = instance.name
798
  build_cmd = lambda arg: ["gnt-instance", "modify", "--disk", arg, name]
799
  AssertCommand(build_cmd("add:size=%s" % size))
800
  AssertCommand(build_cmd("remove"))
801

    
802

    
803
@InstanceCheck(INST_DOWN, INST_DOWN, FIRST_ARG)
804
def TestInstanceGrowDisk(instance):
805
  """gnt-instance grow-disk"""
806
  if qa_config.GetExclusiveStorage():
807
    print qa_utils.FormatInfo("Test not supported with exclusive_storage")
808
    return
809

    
810
  if instance.disk_template == constants.DT_DISKLESS:
811
    print qa_utils.FormatInfo("Test not supported for diskless instances")
812
    return
813

    
814
  name = instance.name
815
  disks = qa_config.GetDiskOptions()
816
  all_size = [d.get("size") for d in disks]
817
  all_grow = [d.get("growth") for d in disks]
818

    
819
  if not all_grow:
820
    # missing disk sizes but instance grow disk has been enabled,
821
    # let's set fixed/nomimal growth
822
    all_grow = ["128M" for _ in all_size]
823

    
824
  for idx, (size, grow) in enumerate(zip(all_size, all_grow)):
825
    # succeed in grow by amount
826
    AssertCommand(["gnt-instance", "grow-disk", name, str(idx), grow])
827
    # fail in grow to the old size
828
    AssertCommand(["gnt-instance", "grow-disk", "--absolute", name, str(idx),
829
                   size], fail=True)
830
    # succeed to grow to old size + 2 * growth
831
    int_size = utils.ParseUnit(size)
832
    int_grow = utils.ParseUnit(grow)
833
    AssertCommand(["gnt-instance", "grow-disk", "--absolute", name, str(idx),
834
                   str(int_size + 2 * int_grow)])
835

    
836

    
837
@InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
838
def TestInstanceDeviceNames(instance):
839
  if instance.disk_template == constants.DT_DISKLESS:
840
    print qa_utils.FormatInfo("Test not supported for diskless instances")
841
    return
842

    
843
  name = instance.name
844
  for dev_type in ["disk", "net"]:
845
    if dev_type == "disk":
846
      options = ",size=512M"
847
    else:
848
      options = ""
849
    # succeed in adding a device named 'test_device'
850
    AssertCommand(["gnt-instance", "modify",
851
                   "--%s=-1:add,name=test_device%s" % (dev_type, options),
852
                   name])
853
    # succeed in removing the 'test_device'
854
    AssertCommand(["gnt-instance", "modify",
855
                   "--%s=test_device:remove" % dev_type,
856
                   name])
857
    # fail to add two devices with the same name
858
    AssertCommand(["gnt-instance", "modify",
859
                   "--%s=-1:add,name=test_device%s" % (dev_type, options),
860
                   "--%s=-1:add,name=test_device%s" % (dev_type, options),
861
                   name], fail=True)
862
    # fail to add a device with invalid name
863
    AssertCommand(["gnt-instance", "modify",
864
                   "--%s=-1:add,name=2%s" % (dev_type, options),
865
                   name], fail=True)
866
  # Rename disks
867
  disks = qa_config.GetDiskOptions()
868
  disk_names = [d.get("name") for d in disks]
869
  for idx, disk_name in enumerate(disk_names):
870
    # Refer to disk by idx
871
    AssertCommand(["gnt-instance", "modify",
872
                   "--disk=%s:modify,name=renamed" % idx,
873
                   name])
874
    # Refer to by name and rename to original name
875
    AssertCommand(["gnt-instance", "modify",
876
                   "--disk=renamed:modify,name=%s" % disk_name,
877
                   name])
878
  if len(disks) >= 2:
879
    # fail in renaming to disks to the same name
880
    AssertCommand(["gnt-instance", "modify",
881
                   "--disk=0:modify,name=same_name",
882
                   "--disk=1:modify,name=same_name",
883
                   name], fail=True)
884

    
885

    
886
def TestInstanceList():
887
  """gnt-instance list"""
888
  qa_utils.GenericQueryTest("gnt-instance", query.INSTANCE_FIELDS.keys())
889

    
890

    
891
def TestInstanceListFields():
892
  """gnt-instance list-fields"""
893
  qa_utils.GenericQueryFieldsTest("gnt-instance", query.INSTANCE_FIELDS.keys())
894

    
895

    
896
@InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
897
def TestInstanceConsole(instance):
898
  """gnt-instance console"""
899
  AssertCommand(["gnt-instance", "console", "--show-cmd", instance.name])
900

    
901

    
902
@InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
903
def TestReplaceDisks(instance, curr_nodes, other_nodes):
904
  """gnt-instance replace-disks"""
905
  def buildcmd(args):
906
    cmd = ["gnt-instance", "replace-disks"]
907
    cmd.extend(args)
908
    cmd.append(instance.name)
909
    return cmd
910

    
911
  if not IsDiskReplacingSupported(instance):
912
    print qa_utils.FormatInfo("Instance doesn't support disk replacing,"
913
                              " skipping test")
914
    return
915

    
916
  # Currently all supported templates have one primary and one secondary node
917
  assert len(curr_nodes) == 2
918
  snode = curr_nodes[1]
919
  assert len(other_nodes) == 1
920
  othernode = other_nodes[0]
921

    
922
  options = qa_config.get("options", {})
923
  use_ialloc = options.get("use-iallocators", True)
924
  for data in [
925
    ["-p"],
926
    ["-s"],
927
    # A placeholder; the actual command choice depends on use_ialloc
928
    None,
929
    # Restore the original secondary
930
    ["--new-secondary=%s" % snode.primary],
931
    ]:
932
    if data is None:
933
      if use_ialloc:
934
        data = ["-I", constants.DEFAULT_IALLOCATOR_SHORTCUT]
935
      else:
936
        data = ["--new-secondary=%s" % othernode.primary]
937
    AssertCommand(buildcmd(data))
938

    
939
  AssertCommand(buildcmd(["-a"]))
940
  AssertCommand(["gnt-instance", "stop", instance.name])
941
  AssertCommand(buildcmd(["-a"]), fail=True)
942
  AssertCommand(["gnt-instance", "activate-disks", instance.name])
943
  AssertCommand(["gnt-instance", "activate-disks", "--wait-for-sync",
944
                 instance.name])
945
  AssertCommand(buildcmd(["-a"]))
946
  AssertCommand(["gnt-instance", "start", instance.name])
947

    
948

    
949
def _AssertRecreateDisks(cmdargs, instance, fail=False, check=True,
950
                         destroy=True):
951
  """Execute gnt-instance recreate-disks and check the result
952

953
  @param cmdargs: Arguments (instance name excluded)
954
  @param instance: Instance to operate on
955
  @param fail: True if the command is expected to fail
956
  @param check: If True and fail is False, check that the disks work
957
  @prama destroy: If True, destroy the old disks first
958

959
  """
960
  if destroy:
961
    _DestroyInstanceDisks(instance)
962
  AssertCommand((["gnt-instance", "recreate-disks"] + cmdargs +
963
                 [instance.name]), fail)
964
  if not fail and check:
965
    # Quick check that the disks are there
966
    AssertCommand(["gnt-instance", "activate-disks", instance.name])
967
    AssertCommand(["gnt-instance", "activate-disks", "--wait-for-sync",
968
                   instance.name])
969
    AssertCommand(["gnt-instance", "deactivate-disks", instance.name])
970

    
971

    
972
@InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
973
def TestRecreateDisks(instance, inodes, othernodes):
974
  """gnt-instance recreate-disks
975

976
  @param instance: Instance to work on
977
  @param inodes: List of the current nodes of the instance
978
  @param othernodes: list/tuple of nodes where to temporarily recreate disks
979

980
  """
981
  options = qa_config.get("options", {})
982
  use_ialloc = options.get("use-iallocators", True)
983
  other_seq = ":".join([n.primary for n in othernodes])
984
  orig_seq = ":".join([n.primary for n in inodes])
985
  # These fail because the instance is running
986
  _AssertRecreateDisks(["-n", other_seq], instance, fail=True, destroy=False)
987
  if use_ialloc:
988
    _AssertRecreateDisks(["-I", "hail"], instance, fail=True, destroy=False)
989
  else:
990
    _AssertRecreateDisks(["-n", other_seq], instance, fail=True, destroy=False)
991
  AssertCommand(["gnt-instance", "stop", instance.name])
992
  # Disks exist: this should fail
993
  _AssertRecreateDisks([], instance, fail=True, destroy=False)
994
  # Recreate disks in place
995
  _AssertRecreateDisks([], instance)
996
  # Move disks away
997
  if use_ialloc:
998
    _AssertRecreateDisks(["-I", "hail"], instance)
999
    # Move disks somewhere else
1000
    _AssertRecreateDisks(["-I", constants.DEFAULT_IALLOCATOR_SHORTCUT],
1001
                         instance)
1002
  else:
1003
    _AssertRecreateDisks(["-n", other_seq], instance)
1004
  # Move disks back
1005
  _AssertRecreateDisks(["-n", orig_seq], instance)
1006
  # Recreate the disks one by one
1007
  for idx in range(0, len(qa_config.GetDiskOptions())):
1008
    # Only the first call should destroy all the disk
1009
    destroy = (idx == 0)
1010
    _AssertRecreateDisks(["--disk=%s" % idx], instance, destroy=destroy,
1011
                         check=False)
1012
  # This and InstanceCheck decoration check that the disks are working
1013
  AssertCommand(["gnt-instance", "reinstall", "-f", instance.name])
1014
  AssertCommand(["gnt-instance", "start", instance.name])
1015

    
1016

    
1017
@InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
1018
def TestInstanceExport(instance, node):
1019
  """gnt-backup export -n ..."""
1020
  name = instance.name
1021
  AssertCommand(["gnt-backup", "export", "-n", node.primary, name])
1022
  return qa_utils.ResolveInstanceName(name)
1023

    
1024

    
1025
@InstanceCheck(None, INST_DOWN, FIRST_ARG)
1026
def TestInstanceExportWithRemove(instance, node):
1027
  """gnt-backup export --remove-instance"""
1028
  AssertCommand(["gnt-backup", "export", "-n", node.primary,
1029
                 "--remove-instance", instance.name])
1030

    
1031

    
1032
@InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
1033
def TestInstanceExportNoTarget(instance):
1034
  """gnt-backup export (without target node, should fail)"""
1035
  AssertCommand(["gnt-backup", "export", instance.name], fail=True)
1036

    
1037

    
1038
@InstanceCheck(None, INST_DOWN, FIRST_ARG)
1039
def TestInstanceImport(newinst, node, expnode, name):
1040
  """gnt-backup import"""
1041
  templ = constants.DT_PLAIN
1042
  cmd = (["gnt-backup", "import",
1043
          "--disk-template=%s" % templ,
1044
          "--no-ip-check",
1045
          "--src-node=%s" % expnode.primary,
1046
          "--src-dir=%s/%s" % (pathutils.EXPORT_DIR, name),
1047
          "--node=%s" % node.primary] +
1048
         _GetGenericAddParameters(newinst, templ,
1049
                                  force_mac=constants.VALUE_GENERATE))
1050
  cmd.append(newinst.name)
1051
  AssertCommand(cmd)
1052
  newinst.SetDiskTemplate(templ)
1053

    
1054

    
1055
def TestBackupList(expnode):
1056
  """gnt-backup list"""
1057
  AssertCommand(["gnt-backup", "list", "--node=%s" % expnode.primary])
1058

    
1059
  qa_utils.GenericQueryTest("gnt-backup", query.EXPORT_FIELDS.keys(),
1060
                            namefield=None, test_unknown=False)
1061

    
1062

    
1063
def TestBackupListFields():
1064
  """gnt-backup list-fields"""
1065
  qa_utils.GenericQueryFieldsTest("gnt-backup", query.EXPORT_FIELDS.keys())
1066

    
1067

    
1068
def TestRemoveInstanceOfflineNode(instance, snode, set_offline, set_online):
1069
  """gnt-instance remove with an off-line node
1070

1071
  @param instance: instance
1072
  @param snode: secondary node, to be set offline
1073
  @param set_offline: function to call to set the node off-line
1074
  @param set_online: function to call to set the node on-line
1075

1076
  """
1077
  info = _GetInstanceInfo(instance.name)
1078
  set_offline(snode)
1079
  try:
1080
    TestInstanceRemove(instance)
1081
  finally:
1082
    set_online(snode)
1083

    
1084
  # Clean up the disks on the offline node, if necessary
1085
  if instance.disk_template not in constants.DTS_EXT_MIRROR:
1086
    # FIXME: abstract the cleanup inside the disks
1087
    if info["storage-type"] == constants.ST_LVM_VG:
1088
      for minor in info["drbd-minors"][snode.primary]:
1089
        AssertCommand(["drbdsetup", str(minor), "down"], node=snode)
1090
      AssertCommand(["lvremove", "-f"] + info["volumes"], node=snode)
1091
    elif info["storage-type"] == constants.ST_FILE:
1092
      filestorage = pathutils.DEFAULT_FILE_STORAGE_DIR
1093
      disk = os.path.join(filestorage, instance.name)
1094
      AssertCommand(["rm", "-rf", disk], node=snode)
1095

    
1096

    
1097
def TestInstanceCreationRestrictedByDiskTemplates():
1098
  """Test adding instances for disabled disk templates."""
1099
  enabled_disk_templates = qa_config.GetEnabledDiskTemplates()
1100
  nodes = qa_config.AcquireManyNodes(2)
1101

    
1102
  # Setup the cluster with the enabled_disk_templates
1103
  AssertCommand(
1104
    ["gnt-cluster", "modify",
1105
     "--enabled-disk-template=%s" %
1106
       ",".join(enabled_disk_templates)],
1107
    fail=False)
1108

    
1109
  # Test instance creation for enabled disk templates
1110
  for disk_template in enabled_disk_templates:
1111
    instance = CreateInstanceByDiskTemplate(nodes, disk_template, fail=False)
1112
    TestInstanceRemove(instance)
1113
    instance.Release()
1114

    
1115
  # Test that instance creation fails for disabled disk templates
1116
  disabled_disk_templates = list(constants.DISK_TEMPLATES
1117
                                 - set(enabled_disk_templates))
1118
  for disk_template in disabled_disk_templates:
1119
    instance = CreateInstanceByDiskTemplate(nodes, disk_template, fail=True)
1120

    
1121
  # Test instance creation for after disabling enabled disk templates
1122
  if (len(enabled_disk_templates) > 1):
1123
    # Partition the disk templates, enable them separately and check if the
1124
    # disabled ones cannot be used by instances.
1125
    middle = len(enabled_disk_templates) / 2
1126
    templates1 = enabled_disk_templates[:middle]
1127
    templates2 = enabled_disk_templates[middle:]
1128

    
1129
    for (enabled, disabled) in [(templates1, templates2),
1130
                                (templates2, templates1)]:
1131
      AssertCommand(["gnt-cluster", "modify",
1132
                     "--enabled-disk-template=%s" %
1133
                       ",".join(enabled)],
1134
                    fail=False)
1135
      for disk_template in disabled:
1136
        CreateInstanceByDiskTemplate(nodes, disk_template, fail=True)
1137
  elif (len(enabled_disk_templates) == 1):
1138
    # If only one disk template is enabled in the QA config, we have to enable
1139
    # some of the disabled disk templates in order to test if the disabling the
1140
    # only enabled disk template prohibits creating instances of that template.
1141
    AssertCommand(["gnt-cluster", "modify",
1142
                   "--enabled-disk-template=%s" %
1143
                     ",".join(disabled_disk_templates)],
1144
                  fail=False)
1145
    CreateInstanceByDiskTemplate(nodes, enabled_disk_templates[0], fail=True)
1146
  else:
1147
    raise qa_error.Error("Please enable at least one disk template"
1148
                         " in your QA setup.")
1149

    
1150
  # Restore initially enabled disk templates
1151
  AssertCommand(["gnt-cluster", "modify",
1152
                 "--enabled-disk-template=%s" %
1153
                   ",".join(enabled_disk_templates)],
1154
                 fail=False)