Statistics
| Branch: | Tag: | Revision:

root / qa / qa_instance.py @ d0a44ec0

History | View | Annotate | Download (28.5 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, size in enumerate(qa_config.get("disk")):
56
      params.extend(["--disk", "%s:size=%s" % (idx, size)])
57

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

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

    
67
  return params
68

    
69

    
70
def _DiskTest(node, disk_template, fail=False):
71
  instance = qa_config.AcquireInstance()
72
  try:
73
    cmd = (["gnt-instance", "add",
74
            "--os-type=%s" % qa_config.get("os"),
75
            "--disk-template=%s" % disk_template,
76
            "--node=%s" % node] +
77
           _GetGenericAddParameters(instance, disk_template))
78
    cmd.append(instance.name)
79

    
80
    AssertCommand(cmd, fail=fail)
81

    
82
    if not fail:
83
      _CheckSsconfInstanceList(instance.name)
84
      instance.SetDiskTemplate(disk_template)
85

    
86
      return instance
87
  except:
88
    instance.Release()
89
    raise
90

    
91
  # Handle the case where creation is expected to fail
92
  assert fail
93
  instance.Release()
94
  return None
95

    
96

    
97
def _GetInstanceInfo(instance):
98
  """Return information about the actual state of an instance.
99

100
  @type instance: string
101
  @param instance: the instance name
102
  @return: a dictionary with the following keys:
103
      - "nodes": instance nodes, a list of strings
104
      - "volumes": instance volume IDs, a list of strings
105
      - "drbd-minors": DRBD minors used by the instance, a dictionary where
106
        keys are nodes, and values are lists of integers (or an empty
107
        dictionary for non-DRBD instances)
108

109
  """
110
  node_elem = r"([^,()]+)(?:\s+\([^)]+\))?"
111
  # re_nodelist matches a list of nodes returned by gnt-instance info, e.g.:
112
  #  node1.fqdn
113
  #  node2.fqdn,node3.fqdn
114
  #  node4.fqdn (group mygroup, group UUID 01234567-abcd-0123-4567-0123456789ab)
115
  # FIXME This works with no more than 2 secondaries
116
  re_nodelist = re.compile(node_elem + "(?:," + node_elem + ")?$")
117

    
118
  info = qa_utils.GetObjectInfo(["gnt-instance", "info", instance])[0]
119
  nodes = []
120
  for nodeinfo in info["Nodes"]:
121
    if "primary" in nodeinfo:
122
      nodes.append(nodeinfo["primary"])
123
    elif "secondaries" in nodeinfo:
124
      nodestr = nodeinfo["secondaries"]
125
      if nodestr:
126
        m = re_nodelist.match(nodestr)
127
        if m:
128
          nodes.extend(filter(None, m.groups()))
129
        else:
130
          nodes.append(nodestr)
131

    
132
  re_drbdnode = re.compile(r"^([^\s,]+),\s+minor=([0-9]+)$")
133
  vols = []
134
  drbd_min = {}
135
  for (count, diskinfo) in enumerate(info["Disks"]):
136
    (dtype, _) = diskinfo["disk/%s" % count].split(",", 1)
137
    if dtype == constants.LD_DRBD8:
138
      for child in diskinfo["child devices"]:
139
        vols.append(child["logical_id"])
140
      for key in ["nodeA", "nodeB"]:
141
        m = re_drbdnode.match(diskinfo[key])
142
        if not m:
143
          raise qa_error.Error("Cannot parse DRBD info: %s" % diskinfo[key])
144
        node = m.group(1)
145
        minor = int(m.group(2))
146
        minorlist = drbd_min.setdefault(node, [])
147
        minorlist.append(minor)
148
    elif dtype == constants.LD_LV:
149
      vols.append(diskinfo["logical_id"])
150

    
151
  assert nodes
152
  assert len(nodes) < 2 or vols
153
  return {
154
    "nodes": nodes,
155
    "volumes": vols,
156
    "drbd-minors": drbd_min,
157
    }
158

    
159

    
160
def _DestroyInstanceVolumes(instance):
161
  """Remove all the LVM volumes of an instance.
162

163
  This is used to simulate HW errors (dead nodes, broken disks...); the
164
  configuration of the instance is not affected.
165
  @type instance: dictionary
166
  @param instance: the instance
167

168
  """
169
  info = _GetInstanceInfo(instance.name)
170
  vols = info["volumes"]
171
  for node in info["nodes"]:
172
    AssertCommand(["lvremove", "-f"] + vols, node=node)
173

    
174

    
175
def _GetInstanceField(instance, field):
176
  """Get the value of a field of an instance.
177

178
  @type instance: string
179
  @param instance: Instance name
180
  @type field: string
181
  @param field: Name of the field
182
  @rtype: string
183

184
  """
185
  master = qa_config.GetMasterNode()
186
  infocmd = utils.ShellQuoteArgs(["gnt-instance", "list", "--no-headers",
187
                                  "--units", "m", "-o", field, instance])
188
  return qa_utils.GetCommandOutput(master.primary, infocmd).strip()
189

    
190

    
191
def _GetBoolInstanceField(instance, field):
192
  """Get the Boolean value of a field of an instance.
193

194
  @type instance: string
195
  @param instance: Instance name
196
  @type field: string
197
  @param field: Name of the field
198
  @rtype: bool
199

200
  """
201
  info_out = _GetInstanceField(instance, field)
202
  if info_out == "Y":
203
    return True
204
  elif info_out == "N":
205
    return False
206
  else:
207
    raise qa_error.Error("Field %s of instance %s has a non-Boolean value:"
208
                         " %s" % (field, instance, info_out))
209

    
210

    
211
def _GetNumInstanceField(instance, field):
212
  """Get a numeric value of a field of an instance.
213

214
  @type instance: string
215
  @param instance: Instance name
216
  @type field: string
217
  @param field: Name of the field
218
  @rtype: int or float
219

220
  """
221
  info_out = _GetInstanceField(instance, field)
222
  try:
223
    ret = int(info_out)
224
  except ValueError:
225
    try:
226
      ret = float(info_out)
227
    except ValueError:
228
      raise qa_error.Error("Field %s of instance %s has a non-numeric value:"
229
                           " %s" % (field, instance, info_out))
230
  return ret
231

    
232

    
233
def GetInstanceSpec(instance, spec):
234
  """Return the current spec for the given parameter.
235

236
  @type instance: string
237
  @param instance: Instance name
238
  @type spec: string
239
  @param spec: one of the supported parameters: "mem-size", "cpu-count",
240
      "disk-count", "disk-size", "nic-count"
241
  @rtype: tuple
242
  @return: (minspec, maxspec); minspec and maxspec can be different only for
243
      memory and disk size
244

245
  """
246
  specmap = {
247
    "mem-size": ["be/minmem", "be/maxmem"],
248
    "cpu-count": ["vcpus"],
249
    "disk-count": ["disk.count"],
250
    "disk-size": ["disk.size/ "],
251
    "nic-count": ["nic.count"],
252
    }
253
  # For disks, first we need the number of disks
254
  if spec == "disk-size":
255
    (numdisk, _) = GetInstanceSpec(instance, "disk-count")
256
    fields = ["disk.size/%s" % k for k in range(0, numdisk)]
257
  else:
258
    assert spec in specmap, "%s not in %s" % (spec, specmap)
259
    fields = specmap[spec]
260
  values = [_GetNumInstanceField(instance, f) for f in fields]
261
  return (min(values), max(values))
262

    
263

    
264
def IsFailoverSupported(instance):
265
  return instance.disk_template in constants.DTS_MIRRORED
266

    
267

    
268
def IsMigrationSupported(instance):
269
  return instance.disk_template in constants.DTS_MIRRORED
270

    
271

    
272
def IsDiskReplacingSupported(instance):
273
  return instance.disk_template == constants.DT_DRBD8
274

    
275

    
276
def TestInstanceAddWithPlainDisk(nodes, fail=False):
277
  """gnt-instance add -t plain"""
278
  assert len(nodes) == 1
279
  instance = _DiskTest(nodes[0].primary, constants.DT_PLAIN, fail=fail)
280
  if not fail:
281
    qa_utils.RunInstanceCheck(instance, True)
282
  return instance
283

    
284

    
285
@InstanceCheck(None, INST_UP, RETURN_VALUE)
286
def TestInstanceAddWithDrbdDisk(nodes):
287
  """gnt-instance add -t drbd"""
288
  assert len(nodes) == 2
289
  return _DiskTest(":".join(map(operator.attrgetter("primary"), nodes)),
290
                   constants.DT_DRBD8)
291

    
292

    
293
@InstanceCheck(None, INST_UP, RETURN_VALUE)
294
def TestInstanceAddFile(nodes):
295
  """gnt-instance add -t file"""
296
  assert len(nodes) == 1
297
  return _DiskTest(nodes[0].primary, constants.DT_FILE)
298

    
299

    
300
@InstanceCheck(None, INST_UP, RETURN_VALUE)
301
def TestInstanceAddDiskless(nodes):
302
  """gnt-instance add -t diskless"""
303
  assert len(nodes) == 1
304
  return _DiskTest(nodes[0].primary, constants.DT_DISKLESS)
305

    
306

    
307
@InstanceCheck(None, INST_DOWN, FIRST_ARG)
308
def TestInstanceRemove(instance):
309
  """gnt-instance remove"""
310
  AssertCommand(["gnt-instance", "remove", "-f", instance.name])
311

    
312

    
313
@InstanceCheck(INST_DOWN, INST_UP, FIRST_ARG)
314
def TestInstanceStartup(instance):
315
  """gnt-instance startup"""
316
  AssertCommand(["gnt-instance", "startup", instance.name])
317

    
318

    
319
@InstanceCheck(INST_UP, INST_DOWN, FIRST_ARG)
320
def TestInstanceShutdown(instance):
321
  """gnt-instance shutdown"""
322
  AssertCommand(["gnt-instance", "shutdown", instance.name])
323

    
324

    
325
@InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
326
def TestInstanceReboot(instance):
327
  """gnt-instance reboot"""
328
  options = qa_config.get("options", {})
329
  reboot_types = options.get("reboot-types", constants.REBOOT_TYPES)
330
  name = instance.name
331
  for rtype in reboot_types:
332
    AssertCommand(["gnt-instance", "reboot", "--type=%s" % rtype, name])
333

    
334
  AssertCommand(["gnt-instance", "shutdown", name])
335
  qa_utils.RunInstanceCheck(instance, False)
336
  AssertCommand(["gnt-instance", "reboot", name])
337

    
338
  master = qa_config.GetMasterNode()
339
  cmd = ["gnt-instance", "list", "--no-headers", "-o", "status", name]
340
  result_output = qa_utils.GetCommandOutput(master.primary,
341
                                            utils.ShellQuoteArgs(cmd))
342
  AssertEqual(result_output.strip(), constants.INSTST_RUNNING)
343

    
344

    
345
@InstanceCheck(INST_DOWN, INST_DOWN, FIRST_ARG)
346
def TestInstanceReinstall(instance):
347
  """gnt-instance reinstall"""
348
  if instance.disk_template == constants.DT_DISKLESS:
349
    print qa_utils.FormatInfo("Test not supported for diskless instances")
350
    return
351

    
352
  AssertCommand(["gnt-instance", "reinstall", "-f", instance.name])
353

    
354
  # Test with non-existant OS definition
355
  AssertCommand(["gnt-instance", "reinstall", "-f",
356
                 "--os-type=NonExistantOsForQa",
357
                 instance.name],
358
                fail=True)
359

    
360

    
361
def _ReadSsconfInstanceList():
362
  """Reads ssconf_instance_list from the master node.
363

364
  """
365
  master = qa_config.GetMasterNode()
366

    
367
  ssconf_path = utils.PathJoin(pathutils.DATA_DIR,
368
                               "ssconf_%s" % constants.SS_INSTANCE_LIST)
369

    
370
  cmd = ["cat", qa_utils.MakeNodePath(master, ssconf_path)]
371

    
372
  return qa_utils.GetCommandOutput(master.primary,
373
                                   utils.ShellQuoteArgs(cmd)).splitlines()
374

    
375

    
376
def _CheckSsconfInstanceList(instance):
377
  """Checks if a certain instance is in the ssconf instance list.
378

379
  @type instance: string
380
  @param instance: Instance name
381

382
  """
383
  AssertIn(qa_utils.ResolveInstanceName(instance),
384
           _ReadSsconfInstanceList())
385

    
386

    
387
@InstanceCheck(INST_DOWN, INST_DOWN, FIRST_ARG)
388
def TestInstanceRenameAndBack(rename_source, rename_target):
389
  """gnt-instance rename
390

391
  This must leave the instance with the original name, not the target
392
  name.
393

394
  """
395
  _CheckSsconfInstanceList(rename_source)
396

    
397
  # first do a rename to a different actual name, expecting it to fail
398
  qa_utils.AddToEtcHosts(["meeeeh-not-exists", rename_target])
399
  try:
400
    AssertCommand(["gnt-instance", "rename", rename_source, rename_target],
401
                  fail=True)
402
    _CheckSsconfInstanceList(rename_source)
403
  finally:
404
    qa_utils.RemoveFromEtcHosts(["meeeeh-not-exists", rename_target])
405

    
406
  # Check instance volume tags correctly updated
407
  # FIXME: this is LVM specific!
408
  info = _GetInstanceInfo(rename_source)
409
  tags_cmd = ("lvs -o tags --noheadings %s | grep " %
410
              (" ".join(info["volumes"]), ))
411

    
412
  # and now rename instance to rename_target...
413
  AssertCommand(["gnt-instance", "rename", rename_source, rename_target])
414
  _CheckSsconfInstanceList(rename_target)
415
  qa_utils.RunInstanceCheck(rename_source, False)
416
  qa_utils.RunInstanceCheck(rename_target, False)
417

    
418
  # NOTE: tags might not be the exactly as the instance name, due to
419
  # charset restrictions; hence the test might be flaky
420
  if rename_source != rename_target:
421
    for node in info["nodes"]:
422
      AssertCommand(tags_cmd + rename_source, node=node, fail=True)
423
      AssertCommand(tags_cmd + rename_target, node=node, fail=False)
424

    
425
  # and back
426
  AssertCommand(["gnt-instance", "rename", rename_target, rename_source])
427
  _CheckSsconfInstanceList(rename_source)
428
  qa_utils.RunInstanceCheck(rename_target, False)
429

    
430
  if rename_source != rename_target:
431
    for node in info["nodes"]:
432
      AssertCommand(tags_cmd + rename_source, node=node, fail=False)
433
      AssertCommand(tags_cmd + rename_target, node=node, fail=True)
434

    
435

    
436
@InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
437
def TestInstanceFailover(instance):
438
  """gnt-instance failover"""
439
  if not IsFailoverSupported(instance):
440
    print qa_utils.FormatInfo("Instance doesn't support failover, skipping"
441
                              " test")
442
    return
443

    
444
  cmd = ["gnt-instance", "failover", "--force", instance.name]
445

    
446
  # failover ...
447
  AssertCommand(cmd)
448
  qa_utils.RunInstanceCheck(instance, True)
449

    
450
  # ... and back
451
  AssertCommand(cmd)
452

    
453

    
454
@InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
455
def TestInstanceMigrate(instance, toggle_always_failover=True):
456
  """gnt-instance migrate"""
457
  if not IsMigrationSupported(instance):
458
    print qa_utils.FormatInfo("Instance doesn't support migration, skipping"
459
                              " test")
460
    return
461

    
462
  cmd = ["gnt-instance", "migrate", "--force", instance.name]
463
  af_par = constants.BE_ALWAYS_FAILOVER
464
  af_field = "be/" + constants.BE_ALWAYS_FAILOVER
465
  af_init_val = _GetBoolInstanceField(instance.name, af_field)
466

    
467
  # migrate ...
468
  AssertCommand(cmd)
469
  # TODO: Verify the choice between failover and migration
470
  qa_utils.RunInstanceCheck(instance, True)
471

    
472
  # ... and back (possibly with always_failover toggled)
473
  if toggle_always_failover:
474
    AssertCommand(["gnt-instance", "modify", "-B",
475
                   ("%s=%s" % (af_par, not af_init_val)),
476
                   instance.name])
477
  AssertCommand(cmd)
478
  # TODO: Verify the choice between failover and migration
479
  qa_utils.RunInstanceCheck(instance, True)
480
  if toggle_always_failover:
481
    AssertCommand(["gnt-instance", "modify", "-B",
482
                   ("%s=%s" % (af_par, af_init_val)), instance.name])
483

    
484
  # TODO: Split into multiple tests
485
  AssertCommand(["gnt-instance", "shutdown", instance.name])
486
  qa_utils.RunInstanceCheck(instance, False)
487
  AssertCommand(cmd, fail=True)
488
  AssertCommand(["gnt-instance", "migrate", "--force", "--allow-failover",
489
                 instance.name])
490
  AssertCommand(["gnt-instance", "start", instance.name])
491
  AssertCommand(cmd)
492
  # @InstanceCheck enforces the check that the instance is running
493
  qa_utils.RunInstanceCheck(instance, True)
494

    
495
  AssertCommand(["gnt-instance", "modify", "-B",
496
                 ("%s=%s" %
497
                  (constants.BE_ALWAYS_FAILOVER, constants.VALUE_TRUE)),
498
                 instance.name])
499

    
500
  AssertCommand(cmd)
501
  qa_utils.RunInstanceCheck(instance, True)
502
  # TODO: Verify that a failover has been done instead of a migration
503

    
504
  # TODO: Verify whether the default value is restored here (not hardcoded)
505
  AssertCommand(["gnt-instance", "modify", "-B",
506
                 ("%s=%s" %
507
                  (constants.BE_ALWAYS_FAILOVER, constants.VALUE_FALSE)),
508
                 instance.name])
509

    
510
  AssertCommand(cmd)
511
  qa_utils.RunInstanceCheck(instance, True)
512

    
513

    
514
def TestInstanceInfo(instance):
515
  """gnt-instance info"""
516
  AssertCommand(["gnt-instance", "info", instance.name])
517

    
518

    
519
@InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
520
def TestInstanceModify(instance):
521
  """gnt-instance modify"""
522
  default_hv = qa_config.GetDefaultHypervisor()
523

    
524
  # Assume /sbin/init exists on all systems
525
  test_kernel = "/sbin/init"
526
  test_initrd = test_kernel
527

    
528
  orig_maxmem = qa_config.get(constants.BE_MAXMEM)
529
  orig_minmem = qa_config.get(constants.BE_MINMEM)
530
  #orig_bridge = qa_config.get("bridge", "xen-br0")
531

    
532
  args = [
533
    ["-B", "%s=128" % constants.BE_MINMEM],
534
    ["-B", "%s=128" % constants.BE_MAXMEM],
535
    ["-B", "%s=%s,%s=%s" % (constants.BE_MINMEM, orig_minmem,
536
                            constants.BE_MAXMEM, orig_maxmem)],
537
    ["-B", "%s=2" % constants.BE_VCPUS],
538
    ["-B", "%s=1" % constants.BE_VCPUS],
539
    ["-B", "%s=%s" % (constants.BE_VCPUS, constants.VALUE_DEFAULT)],
540
    ["-B", "%s=%s" % (constants.BE_ALWAYS_FAILOVER, constants.VALUE_TRUE)],
541
    ["-B", "%s=%s" % (constants.BE_ALWAYS_FAILOVER, constants.VALUE_DEFAULT)],
542

    
543
    ["-H", "%s=%s" % (constants.HV_KERNEL_PATH, test_kernel)],
544
    ["-H", "%s=%s" % (constants.HV_KERNEL_PATH, constants.VALUE_DEFAULT)],
545

    
546
    # TODO: bridge tests
547
    #["--bridge", "xen-br1"],
548
    #["--bridge", orig_bridge],
549
    ]
550

    
551
  if default_hv == constants.HT_XEN_PVM:
552
    args.extend([
553
      ["-H", "%s=%s" % (constants.HV_INITRD_PATH, test_initrd)],
554
      ["-H", "no_%s" % (constants.HV_INITRD_PATH, )],
555
      ["-H", "%s=%s" % (constants.HV_INITRD_PATH, constants.VALUE_DEFAULT)],
556
      ])
557
  elif default_hv == constants.HT_XEN_HVM:
558
    args.extend([
559
      ["-H", "%s=acn" % constants.HV_BOOT_ORDER],
560
      ["-H", "%s=%s" % (constants.HV_BOOT_ORDER, constants.VALUE_DEFAULT)],
561
      ])
562

    
563
  for alist in args:
564
    AssertCommand(["gnt-instance", "modify"] + alist + [instance.name])
565

    
566
  # check no-modify
567
  AssertCommand(["gnt-instance", "modify", instance.name], fail=True)
568

    
569
  # Marking offline while instance is running must fail...
570
  AssertCommand(["gnt-instance", "modify", "--offline", instance.name],
571
                 fail=True)
572

    
573
  # ...while making it online is ok, and should work
574
  AssertCommand(["gnt-instance", "modify", "--online", instance.name])
575

    
576

    
577
@InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
578
def TestInstanceModifyPrimaryAndBack(instance, currentnode, othernode):
579
  """gnt-instance modify --new-primary
580

581
  This will leave the instance on its original primary node, not other node.
582

583
  """
584
  if instance.disk_template != constants.DT_FILE:
585
    print qa_utils.FormatInfo("Test only supported for the file disk template")
586
    return
587

    
588
  name = instance.name
589
  current = currentnode.primary
590
  other = othernode.primary
591

    
592
  filestorage = qa_config.get("file-storage-dir")
593
  disk = os.path.join(filestorage, name)
594

    
595
  AssertCommand(["gnt-instance", "modify", "--new-primary=%s" % other, name],
596
                fail=True)
597
  AssertCommand(["gnt-instance", "shutdown", name])
598
  AssertCommand(["scp", "-r", disk, "%s:%s" % (other, filestorage)])
599
  AssertCommand(["gnt-instance", "modify", "--new-primary=%s" % other, name])
600
  AssertCommand(["gnt-instance", "startup", name])
601

    
602
  # and back
603
  AssertCommand(["gnt-instance", "shutdown", name])
604
  AssertCommand(["rm", "-rf", disk], node=other)
605
  AssertCommand(["gnt-instance", "modify", "--new-primary=%s" % current, name])
606
  AssertCommand(["gnt-instance", "startup", name])
607

    
608

    
609
@InstanceCheck(INST_DOWN, INST_DOWN, FIRST_ARG)
610
def TestInstanceStoppedModify(instance):
611
  """gnt-instance modify (stopped instance)"""
612
  name = instance.name
613

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

    
617
  # Mark instance as offline
618
  AssertCommand(["gnt-instance", "modify", "--offline", name])
619

    
620
  # When the instance is offline shutdown should only work with --force,
621
  # while start should never work
622
  AssertCommand(["gnt-instance", "shutdown", name], fail=True)
623
  AssertCommand(["gnt-instance", "shutdown", "--force", name])
624
  AssertCommand(["gnt-instance", "start", name], fail=True)
625
  AssertCommand(["gnt-instance", "start", "--force", name], fail=True)
626

    
627
  # Also do offline to offline
628
  AssertCommand(["gnt-instance", "modify", "--offline", name])
629

    
630
  # And online again
631
  AssertCommand(["gnt-instance", "modify", "--online", name])
632

    
633

    
634
@InstanceCheck(INST_DOWN, INST_DOWN, FIRST_ARG)
635
def TestInstanceConvertDiskToPlain(instance, inodes):
636
  """gnt-instance modify -t"""
637
  name = instance.name
638

    
639
  template = instance.disk_template
640
  if template != constants.DT_DRBD8:
641
    print qa_utils.FormatInfo("Unsupported template %s, skipping conversion"
642
                              " test" % template)
643
    return
644

    
645
  assert len(inodes) == 2
646
  AssertCommand(["gnt-instance", "modify", "-t", constants.DT_PLAIN, name])
647
  AssertCommand(["gnt-instance", "modify", "-t", constants.DT_DRBD8,
648
                 "-n", inodes[1].primary, name])
649

    
650

    
651
@InstanceCheck(INST_DOWN, INST_DOWN, FIRST_ARG)
652
def TestInstanceGrowDisk(instance):
653
  """gnt-instance grow-disk"""
654
  if qa_config.GetExclusiveStorage():
655
    print qa_utils.FormatInfo("Test not supported with exclusive_storage")
656
    return
657

    
658
  if instance.disk_template == constants.DT_DISKLESS:
659
    print qa_utils.FormatInfo("Test not supported for diskless instances")
660
    return
661

    
662
  name = instance.name
663
  all_size = qa_config.get("disk")
664
  all_grow = qa_config.get("disk-growth")
665

    
666
  if not all_grow:
667
    # missing disk sizes but instance grow disk has been enabled,
668
    # let's set fixed/nomimal growth
669
    all_grow = ["128M" for _ in all_size]
670

    
671
  for idx, (size, grow) in enumerate(zip(all_size, all_grow)):
672
    # succeed in grow by amount
673
    AssertCommand(["gnt-instance", "grow-disk", name, str(idx), grow])
674
    # fail in grow to the old size
675
    AssertCommand(["gnt-instance", "grow-disk", "--absolute", name, str(idx),
676
                   size], fail=True)
677
    # succeed to grow to old size + 2 * growth
678
    int_size = utils.ParseUnit(size)
679
    int_grow = utils.ParseUnit(grow)
680
    AssertCommand(["gnt-instance", "grow-disk", "--absolute", name, str(idx),
681
                   str(int_size + 2 * int_grow)])
682

    
683

    
684
def TestInstanceList():
685
  """gnt-instance list"""
686
  qa_utils.GenericQueryTest("gnt-instance", query.INSTANCE_FIELDS.keys())
687

    
688

    
689
def TestInstanceListFields():
690
  """gnt-instance list-fields"""
691
  qa_utils.GenericQueryFieldsTest("gnt-instance", query.INSTANCE_FIELDS.keys())
692

    
693

    
694
@InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
695
def TestInstanceConsole(instance):
696
  """gnt-instance console"""
697
  AssertCommand(["gnt-instance", "console", "--show-cmd", instance.name])
698

    
699

    
700
@InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
701
def TestReplaceDisks(instance, curr_nodes, other_nodes):
702
  """gnt-instance replace-disks"""
703
  def buildcmd(args):
704
    cmd = ["gnt-instance", "replace-disks"]
705
    cmd.extend(args)
706
    cmd.append(instance.name)
707
    return cmd
708

    
709
  if not IsDiskReplacingSupported(instance):
710
    print qa_utils.FormatInfo("Instance doesn't support disk replacing,"
711
                              " skipping test")
712
    return
713

    
714
  # Currently all supported templates have one primary and one secondary node
715
  assert len(curr_nodes) == 2
716
  snode = curr_nodes[1]
717
  assert len(other_nodes) == 1
718
  othernode = other_nodes[0]
719

    
720
  options = qa_config.get("options", {})
721
  use_ialloc = options.get("use-iallocators", True)
722
  for data in [
723
    ["-p"],
724
    ["-s"],
725
    # A placeholder; the actual command choice depends on use_ialloc
726
    None,
727
    # Restore the original secondary
728
    ["--new-secondary=%s" % snode.primary],
729
    ]:
730
    if data is None:
731
      if use_ialloc:
732
        data = ["-I", constants.DEFAULT_IALLOCATOR_SHORTCUT]
733
      else:
734
        data = ["--new-secondary=%s" % othernode.primary]
735
    AssertCommand(buildcmd(data))
736

    
737
  AssertCommand(buildcmd(["-a"]))
738
  AssertCommand(["gnt-instance", "stop", instance.name])
739
  AssertCommand(buildcmd(["-a"]), fail=True)
740
  AssertCommand(["gnt-instance", "activate-disks", instance.name])
741
  AssertCommand(["gnt-instance", "activate-disks", "--wait-for-sync",
742
                 instance.name])
743
  AssertCommand(buildcmd(["-a"]))
744
  AssertCommand(["gnt-instance", "start", instance.name])
745

    
746

    
747
def _AssertRecreateDisks(cmdargs, instance, fail=False, check=True,
748
                         destroy=True):
749
  """Execute gnt-instance recreate-disks and check the result
750

751
  @param cmdargs: Arguments (instance name excluded)
752
  @param instance: Instance to operate on
753
  @param fail: True if the command is expected to fail
754
  @param check: If True and fail is False, check that the disks work
755
  @prama destroy: If True, destroy the old disks first
756

757
  """
758
  if destroy:
759
    _DestroyInstanceVolumes(instance)
760
  AssertCommand((["gnt-instance", "recreate-disks"] + cmdargs +
761
                 [instance.name]), fail)
762
  if not fail and check:
763
    # Quick check that the disks are there
764
    AssertCommand(["gnt-instance", "activate-disks", instance.name])
765
    AssertCommand(["gnt-instance", "activate-disks", "--wait-for-sync",
766
                   instance.name])
767
    AssertCommand(["gnt-instance", "deactivate-disks", instance.name])
768

    
769

    
770
@InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
771
def TestRecreateDisks(instance, inodes, othernodes):
772
  """gnt-instance recreate-disks
773

774
  @param instance: Instance to work on
775
  @param inodes: List of the current nodes of the instance
776
  @param othernodes: list/tuple of nodes where to temporarily recreate disks
777

778
  """
779
  options = qa_config.get("options", {})
780
  use_ialloc = options.get("use-iallocators", True)
781
  other_seq = ":".join([n.primary for n in othernodes])
782
  orig_seq = ":".join([n.primary for n in inodes])
783
  # These fail because the instance is running
784
  _AssertRecreateDisks(["-n", other_seq], instance, fail=True, destroy=False)
785
  if use_ialloc:
786
    _AssertRecreateDisks(["-I", "hail"], instance, fail=True, destroy=False)
787
  else:
788
    _AssertRecreateDisks(["-n", other_seq], instance, fail=True, destroy=False)
789
  AssertCommand(["gnt-instance", "stop", instance.name])
790
  # Disks exist: this should fail
791
  _AssertRecreateDisks([], instance, fail=True, destroy=False)
792
  # Recreate disks in place
793
  _AssertRecreateDisks([], instance)
794
  # Move disks away
795
  if use_ialloc:
796
    _AssertRecreateDisks(["-I", "hail"], instance)
797
    # Move disks somewhere else
798
    _AssertRecreateDisks(["-I", constants.DEFAULT_IALLOCATOR_SHORTCUT],
799
                         instance)
800
  else:
801
    _AssertRecreateDisks(["-n", other_seq], instance)
802
  # Move disks back
803
  _AssertRecreateDisks(["-n", orig_seq], instance, check=False)
804
  # This and InstanceCheck decoration check that the disks are working
805
  AssertCommand(["gnt-instance", "reinstall", "-f", instance.name])
806
  AssertCommand(["gnt-instance", "start", instance.name])
807

    
808

    
809
@InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
810
def TestInstanceExport(instance, node):
811
  """gnt-backup export -n ..."""
812
  name = instance.name
813
  AssertCommand(["gnt-backup", "export", "-n", node.primary, name])
814
  return qa_utils.ResolveInstanceName(name)
815

    
816

    
817
@InstanceCheck(None, INST_DOWN, FIRST_ARG)
818
def TestInstanceExportWithRemove(instance, node):
819
  """gnt-backup export --remove-instance"""
820
  AssertCommand(["gnt-backup", "export", "-n", node.primary,
821
                 "--remove-instance", instance.name])
822

    
823

    
824
@InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
825
def TestInstanceExportNoTarget(instance):
826
  """gnt-backup export (without target node, should fail)"""
827
  AssertCommand(["gnt-backup", "export", instance.name], fail=True)
828

    
829

    
830
@InstanceCheck(None, INST_DOWN, FIRST_ARG)
831
def TestInstanceImport(newinst, node, expnode, name):
832
  """gnt-backup import"""
833
  templ = constants.DT_PLAIN
834
  cmd = (["gnt-backup", "import",
835
          "--disk-template=%s" % templ,
836
          "--no-ip-check",
837
          "--src-node=%s" % expnode.primary,
838
          "--src-dir=%s/%s" % (pathutils.EXPORT_DIR, name),
839
          "--node=%s" % node.primary] +
840
         _GetGenericAddParameters(newinst, templ,
841
                                  force_mac=constants.VALUE_GENERATE))
842
  cmd.append(newinst.name)
843
  AssertCommand(cmd)
844
  newinst.SetDiskTemplate(templ)
845

    
846

    
847
def TestBackupList(expnode):
848
  """gnt-backup list"""
849
  AssertCommand(["gnt-backup", "list", "--node=%s" % expnode.primary])
850

    
851
  qa_utils.GenericQueryTest("gnt-backup", query.EXPORT_FIELDS.keys(),
852
                            namefield=None, test_unknown=False)
853

    
854

    
855
def TestBackupListFields():
856
  """gnt-backup list-fields"""
857
  qa_utils.GenericQueryFieldsTest("gnt-backup", query.EXPORT_FIELDS.keys())
858

    
859

    
860
def TestRemoveInstanceOfflineNode(instance, snode, set_offline, set_online):
861
  """gtn-instance remove with an off-line node
862

863
  @param instance: instance
864
  @param snode: secondary node, to be set offline
865
  @param set_offline: function to call to set the node off-line
866
  @param set_online: function to call to set the node on-line
867

868
  """
869
  info = _GetInstanceInfo(instance.name)
870
  set_offline(snode)
871
  try:
872
    TestInstanceRemove(instance)
873
  finally:
874
    set_online(snode)
875
  # Clean up the disks on the offline node
876
  for minor in info["drbd-minors"][snode.primary]:
877
    AssertCommand(["drbdsetup", str(minor), "down"], node=snode)
878
  AssertCommand(["lvremove", "-f"] + info["volumes"], node=snode)