Statistics
| Branch: | Tag: | Revision:

root / qa / qa_instance.py @ 6f88e076

History | View | Annotate | Download (24.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 re
28

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

    
34
import qa_config
35
import qa_utils
36
import qa_error
37

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

    
41

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

    
45

    
46
def _GetGenericAddParameters(inst, force_mac=None):
47
  params = ["-B"]
48
  params.append("%s=%s,%s=%s" % (constants.BE_MINMEM,
49
                                 qa_config.get(constants.BE_MINMEM),
50
                                 constants.BE_MAXMEM,
51
                                 qa_config.get(constants.BE_MAXMEM)))
52
  for idx, size in enumerate(qa_config.get("disk")):
53
    params.extend(["--disk", "%s:size=%s" % (idx, size)])
54

    
55
  # Set static MAC address if configured
56
  if force_mac:
57
    nic0_mac = force_mac
58
  else:
59
    nic0_mac = qa_config.GetInstanceNicMac(inst)
60
  if nic0_mac:
61
    params.extend(["--net", "0:mac=%s" % nic0_mac])
62

    
63
  return params
64

    
65

    
66
def _DiskTest(node, disk_template):
67
  instance = qa_config.AcquireInstance()
68
  try:
69
    cmd = (["gnt-instance", "add",
70
            "--os-type=%s" % qa_config.get("os"),
71
            "--disk-template=%s" % disk_template,
72
            "--node=%s" % node] +
73
           _GetGenericAddParameters(instance))
74
    cmd.append(instance["name"])
75

    
76
    AssertCommand(cmd)
77

    
78
    _CheckSsconfInstanceList(instance["name"])
79
    qa_config.SetInstanceTemplate(instance, disk_template)
80

    
81
    return instance
82
  except:
83
    instance.Release()
84
    raise
85

    
86

    
87
def _GetInstanceInfo(instance):
88
  """Return information about the actual state of an instance.
89

90
  @type instance: string
91
  @param instance: the instance name
92
  @return: a dictionary with the following keys:
93
      - "nodes": instance nodes, a list of strings
94
      - "volumes": instance volume IDs, a list of strings
95
      - "drbd-minors": DRBD minors used by the instance, a dictionary where
96
        keys are nodes, and values are lists of integers (or an empty
97
        dictionary for non-DRBD instances)
98

99
  """
100
  master = qa_config.GetMasterNode()
101
  infocmd = utils.ShellQuoteArgs(["gnt-instance", "info", instance])
102
  info_out = qa_utils.GetCommandOutput(master["primary"], infocmd)
103
  re_node = re.compile(r"^\s+-\s+(?:primary|secondaries):\s+(\S.+)$")
104
  node_elem = r"([^,()]+)(?:\s+\([^)]+\))?"
105
  # re_nodelist matches a list of nodes returned by gnt-instance info, e.g.:
106
  #  node1.fqdn
107
  #  node2.fqdn,node3.fqdn
108
  #  node4.fqdn (group mygroup, group UUID 01234567-abcd-0123-4567-0123456789ab)
109
  # FIXME This works with no more than 2 secondaries
110
  re_nodelist = re.compile(node_elem + "(?:," + node_elem + ")?$")
111
  re_vol = re.compile(r"^\s+logical_id:\s+(\S+)$")
112
  re_drbdnode = re.compile(r"^\s+node[AB]:\s+([^\s,]+),\s+minor=([0-9]+)$")
113
  nodes = []
114
  vols = []
115
  drbd_min = {}
116
  for line in info_out.splitlines():
117
    m = re_node.match(line)
118
    if m:
119
      nodestr = m.group(1)
120
      m2 = re_nodelist.match(nodestr)
121
      if m2:
122
        nodes.extend(filter(None, m2.groups()))
123
      else:
124
        nodes.append(nodestr)
125
    m = re_vol.match(line)
126
    if m:
127
      vols.append(m.group(1))
128
    m = re_drbdnode.match(line)
129
    if m:
130
      node = m.group(1)
131
      minor = int(m.group(2))
132
      if drbd_min.get(node) is not None:
133
        drbd_min[node].append(minor)
134
      else:
135
        drbd_min[node] = [minor]
136
  assert vols
137
  assert nodes
138
  return {"nodes": nodes, "volumes": vols, "drbd-minors": drbd_min}
139

    
140

    
141
def _DestroyInstanceVolumes(instance):
142
  """Remove all the LVM volumes of an instance.
143

144
  This is used to simulate HW errors (dead nodes, broken disks...); the
145
  configuration of the instance is not affected.
146
  @type instance: dictionary
147
  @param instance: the instance
148

149
  """
150
  info = _GetInstanceInfo(instance["name"])
151
  vols = info["volumes"]
152
  for node in info["nodes"]:
153
    AssertCommand(["lvremove", "-f"] + vols, node=node)
154

    
155

    
156
def _GetBoolInstanceField(instance, field):
157
  """Get the Boolean value of a field of an instance.
158

159
  @type instance: string
160
  @param instance: Instance name
161
  @type field: string
162
  @param field: Name of the field
163

164
  """
165
  master = qa_config.GetMasterNode()
166
  infocmd = utils.ShellQuoteArgs(["gnt-instance", "list", "--no-headers",
167
                                  "-o", field, instance])
168
  info_out = qa_utils.GetCommandOutput(master["primary"], infocmd).strip()
169
  if info_out == "Y":
170
    return True
171
  elif info_out == "N":
172
    return False
173
  else:
174
    raise qa_error.Error("Field %s of instance %s has a non-Boolean value:"
175
                         " %s" % (field, instance, info_out))
176

    
177

    
178
def IsFailoverSupported(instance):
179
  templ = qa_config.GetInstanceTemplate(instance)
180
  return templ in constants.DTS_MIRRORED
181

    
182

    
183
def IsMigrationSupported(instance):
184
  templ = qa_config.GetInstanceTemplate(instance)
185
  return templ in constants.DTS_MIRRORED
186

    
187

    
188
def IsDiskReplacingSupported(instance):
189
  templ = qa_config.GetInstanceTemplate(instance)
190
  return templ == constants.DT_DRBD8
191

    
192

    
193
@InstanceCheck(None, INST_UP, RETURN_VALUE)
194
def TestInstanceAddWithPlainDisk(nodes):
195
  """gnt-instance add -t plain"""
196
  assert len(nodes) == 1
197
  return _DiskTest(nodes[0]["primary"], "plain")
198

    
199

    
200
@InstanceCheck(None, INST_UP, RETURN_VALUE)
201
def TestInstanceAddWithDrbdDisk(nodes):
202
  """gnt-instance add -t drbd"""
203
  assert len(nodes) == 2
204
  return _DiskTest(":".join(map(operator.itemgetter("primary"), nodes)),
205
                   "drbd")
206

    
207

    
208
@InstanceCheck(None, INST_DOWN, FIRST_ARG)
209
def TestInstanceRemove(instance):
210
  """gnt-instance remove"""
211
  AssertCommand(["gnt-instance", "remove", "-f", instance["name"]])
212

    
213

    
214
@InstanceCheck(INST_DOWN, INST_UP, FIRST_ARG)
215
def TestInstanceStartup(instance):
216
  """gnt-instance startup"""
217
  AssertCommand(["gnt-instance", "startup", instance["name"]])
218

    
219

    
220
@InstanceCheck(INST_UP, INST_DOWN, FIRST_ARG)
221
def TestInstanceShutdown(instance):
222
  """gnt-instance shutdown"""
223
  AssertCommand(["gnt-instance", "shutdown", instance["name"]])
224

    
225

    
226
@InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
227
def TestInstanceReboot(instance):
228
  """gnt-instance reboot"""
229
  options = qa_config.get("options", {})
230
  reboot_types = options.get("reboot-types", constants.REBOOT_TYPES)
231
  name = instance["name"]
232
  for rtype in reboot_types:
233
    AssertCommand(["gnt-instance", "reboot", "--type=%s" % rtype, name])
234

    
235
  AssertCommand(["gnt-instance", "shutdown", name])
236
  qa_utils.RunInstanceCheck(instance, False)
237
  AssertCommand(["gnt-instance", "reboot", name])
238

    
239
  master = qa_config.GetMasterNode()
240
  cmd = ["gnt-instance", "list", "--no-headers", "-o", "status", name]
241
  result_output = qa_utils.GetCommandOutput(master["primary"],
242
                                            utils.ShellQuoteArgs(cmd))
243
  AssertEqual(result_output.strip(), constants.INSTST_RUNNING)
244

    
245

    
246
@InstanceCheck(INST_DOWN, INST_DOWN, FIRST_ARG)
247
def TestInstanceReinstall(instance):
248
  """gnt-instance reinstall"""
249
  AssertCommand(["gnt-instance", "reinstall", "-f", instance["name"]])
250

    
251
  # Test with non-existant OS definition
252
  AssertCommand(["gnt-instance", "reinstall", "-f",
253
                 "--os-type=NonExistantOsForQa",
254
                 instance["name"]],
255
                fail=True)
256

    
257

    
258
def _ReadSsconfInstanceList():
259
  """Reads ssconf_instance_list from the master node.
260

261
  """
262
  master = qa_config.GetMasterNode()
263

    
264
  cmd = ["cat", utils.PathJoin(pathutils.DATA_DIR,
265
                               "ssconf_%s" % constants.SS_INSTANCE_LIST)]
266

    
267
  return qa_utils.GetCommandOutput(master["primary"],
268
                                   utils.ShellQuoteArgs(cmd)).splitlines()
269

    
270

    
271
def _CheckSsconfInstanceList(instance):
272
  """Checks if a certain instance is in the ssconf instance list.
273

274
  @type instance: string
275
  @param instance: Instance name
276

277
  """
278
  AssertIn(qa_utils.ResolveInstanceName(instance),
279
           _ReadSsconfInstanceList())
280

    
281

    
282
@InstanceCheck(INST_DOWN, INST_DOWN, FIRST_ARG)
283
def TestInstanceRenameAndBack(rename_source, rename_target):
284
  """gnt-instance rename
285

286
  This must leave the instance with the original name, not the target
287
  name.
288

289
  """
290
  _CheckSsconfInstanceList(rename_source)
291

    
292
  # first do a rename to a different actual name, expecting it to fail
293
  qa_utils.AddToEtcHosts(["meeeeh-not-exists", rename_target])
294
  try:
295
    AssertCommand(["gnt-instance", "rename", rename_source, rename_target],
296
                  fail=True)
297
    _CheckSsconfInstanceList(rename_source)
298
  finally:
299
    qa_utils.RemoveFromEtcHosts(["meeeeh-not-exists", rename_target])
300

    
301
  # Check instance volume tags correctly updated
302
  # FIXME: this is LVM specific!
303
  info = _GetInstanceInfo(rename_source)
304
  tags_cmd = ("lvs -o tags --noheadings %s | grep " %
305
              (" ".join(info["volumes"]), ))
306

    
307
  # and now rename instance to rename_target...
308
  AssertCommand(["gnt-instance", "rename", rename_source, rename_target])
309
  _CheckSsconfInstanceList(rename_target)
310
  qa_utils.RunInstanceCheck(rename_source, False)
311
  qa_utils.RunInstanceCheck(rename_target, False)
312

    
313
  # NOTE: tags might not be the exactly as the instance name, due to
314
  # charset restrictions; hence the test might be flaky
315
  if rename_source != rename_target:
316
    for node in info["nodes"]:
317
      AssertCommand(tags_cmd + rename_source, node=node, fail=True)
318
      AssertCommand(tags_cmd + rename_target, node=node, fail=False)
319

    
320
  # and back
321
  AssertCommand(["gnt-instance", "rename", rename_target, rename_source])
322
  _CheckSsconfInstanceList(rename_source)
323
  qa_utils.RunInstanceCheck(rename_target, False)
324

    
325
  if rename_source != rename_target:
326
    for node in info["nodes"]:
327
      AssertCommand(tags_cmd + rename_source, node=node, fail=False)
328
      AssertCommand(tags_cmd + rename_target, node=node, fail=True)
329

    
330

    
331
@InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
332
def TestInstanceFailover(instance):
333
  """gnt-instance failover"""
334
  if not IsFailoverSupported(instance):
335
    print qa_utils.FormatInfo("Instance doesn't support failover, skipping"
336
                              " test")
337
    return
338

    
339
  cmd = ["gnt-instance", "failover", "--force", instance["name"]]
340

    
341
  # failover ...
342
  AssertCommand(cmd)
343
  qa_utils.RunInstanceCheck(instance, True)
344

    
345
  # ... and back
346
  AssertCommand(cmd)
347

    
348

    
349
@InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
350
def TestInstanceMigrate(instance, toggle_always_failover=True):
351
  """gnt-instance migrate"""
352
  if not IsMigrationSupported(instance):
353
    print qa_utils.FormatInfo("Instance doesn't support migration, skipping"
354
                              " test")
355
    return
356

    
357
  cmd = ["gnt-instance", "migrate", "--force", instance["name"]]
358
  af_par = constants.BE_ALWAYS_FAILOVER
359
  af_field = "be/" + constants.BE_ALWAYS_FAILOVER
360
  af_init_val = _GetBoolInstanceField(instance["name"], af_field)
361

    
362
  # migrate ...
363
  AssertCommand(cmd)
364
  # TODO: Verify the choice between failover and migration
365
  qa_utils.RunInstanceCheck(instance, True)
366

    
367
  # ... and back (possibly with always_failover toggled)
368
  if toggle_always_failover:
369
    AssertCommand(["gnt-instance", "modify", "-B",
370
                   ("%s=%s" % (af_par, not af_init_val)),
371
                   instance["name"]])
372
  AssertCommand(cmd)
373
  # TODO: Verify the choice between failover and migration
374
  qa_utils.RunInstanceCheck(instance, True)
375
  if toggle_always_failover:
376
    AssertCommand(["gnt-instance", "modify", "-B",
377
                   ("%s=%s" % (af_par, af_init_val)), instance["name"]])
378

    
379
  # TODO: Split into multiple tests
380
  AssertCommand(["gnt-instance", "shutdown", instance["name"]])
381
  qa_utils.RunInstanceCheck(instance, False)
382
  AssertCommand(cmd, fail=True)
383
  AssertCommand(["gnt-instance", "migrate", "--force", "--allow-failover",
384
                 instance["name"]])
385
  AssertCommand(["gnt-instance", "start", instance["name"]])
386
  AssertCommand(cmd)
387
  # @InstanceCheck enforces the check that the instance is running
388
  qa_utils.RunInstanceCheck(instance, True)
389

    
390
  AssertCommand(["gnt-instance", "modify", "-B",
391
                 ("%s=%s" %
392
                  (constants.BE_ALWAYS_FAILOVER, constants.VALUE_TRUE)),
393
                 instance["name"]])
394

    
395
  AssertCommand(cmd)
396
  qa_utils.RunInstanceCheck(instance, True)
397
  # TODO: Verify that a failover has been done instead of a migration
398

    
399
  # TODO: Verify whether the default value is restored here (not hardcoded)
400
  AssertCommand(["gnt-instance", "modify", "-B",
401
                 ("%s=%s" %
402
                  (constants.BE_ALWAYS_FAILOVER, constants.VALUE_FALSE)),
403
                 instance["name"]])
404

    
405
  AssertCommand(cmd)
406
  qa_utils.RunInstanceCheck(instance, True)
407

    
408

    
409
def TestInstanceInfo(instance):
410
  """gnt-instance info"""
411
  AssertCommand(["gnt-instance", "info", instance["name"]])
412

    
413

    
414
@InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
415
def TestInstanceModify(instance):
416
  """gnt-instance modify"""
417
  default_hv = qa_config.GetDefaultHypervisor()
418

    
419
  # Assume /sbin/init exists on all systems
420
  test_kernel = "/sbin/init"
421
  test_initrd = test_kernel
422

    
423
  orig_maxmem = qa_config.get(constants.BE_MAXMEM)
424
  orig_minmem = qa_config.get(constants.BE_MINMEM)
425
  #orig_bridge = qa_config.get("bridge", "xen-br0")
426

    
427
  args = [
428
    ["-B", "%s=128" % constants.BE_MINMEM],
429
    ["-B", "%s=128" % constants.BE_MAXMEM],
430
    ["-B", "%s=%s,%s=%s" % (constants.BE_MINMEM, orig_minmem,
431
                            constants.BE_MAXMEM, orig_maxmem)],
432
    ["-B", "%s=2" % constants.BE_VCPUS],
433
    ["-B", "%s=1" % constants.BE_VCPUS],
434
    ["-B", "%s=%s" % (constants.BE_VCPUS, constants.VALUE_DEFAULT)],
435
    ["-B", "%s=%s" % (constants.BE_ALWAYS_FAILOVER, constants.VALUE_TRUE)],
436
    ["-B", "%s=%s" % (constants.BE_ALWAYS_FAILOVER, constants.VALUE_DEFAULT)],
437

    
438
    ["-H", "%s=%s" % (constants.HV_KERNEL_PATH, test_kernel)],
439
    ["-H", "%s=%s" % (constants.HV_KERNEL_PATH, constants.VALUE_DEFAULT)],
440

    
441
    # TODO: bridge tests
442
    #["--bridge", "xen-br1"],
443
    #["--bridge", orig_bridge],
444
    ]
445

    
446
  if default_hv == constants.HT_XEN_PVM:
447
    args.extend([
448
      ["-H", "%s=%s" % (constants.HV_INITRD_PATH, test_initrd)],
449
      ["-H", "no_%s" % (constants.HV_INITRD_PATH, )],
450
      ["-H", "%s=%s" % (constants.HV_INITRD_PATH, constants.VALUE_DEFAULT)],
451
      ])
452
  elif default_hv == constants.HT_XEN_HVM:
453
    args.extend([
454
      ["-H", "%s=acn" % constants.HV_BOOT_ORDER],
455
      ["-H", "%s=%s" % (constants.HV_BOOT_ORDER, constants.VALUE_DEFAULT)],
456
      ])
457

    
458
  for alist in args:
459
    AssertCommand(["gnt-instance", "modify"] + alist + [instance["name"]])
460

    
461
  # check no-modify
462
  AssertCommand(["gnt-instance", "modify", instance["name"]], fail=True)
463

    
464
  # Marking offline while instance is running must fail...
465
  AssertCommand(["gnt-instance", "modify", "--offline", instance["name"]],
466
                 fail=True)
467

    
468
  # ...while making it online is ok, and should work
469
  AssertCommand(["gnt-instance", "modify", "--online", instance["name"]])
470

    
471

    
472
@InstanceCheck(INST_DOWN, INST_DOWN, FIRST_ARG)
473
def TestInstanceStoppedModify(instance):
474
  """gnt-instance modify (stopped instance)"""
475
  name = instance["name"]
476

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

    
480
  # Mark instance as offline
481
  AssertCommand(["gnt-instance", "modify", "--offline", name])
482

    
483
  # When the instance is offline shutdown should only work with --force,
484
  # while start should never work
485
  AssertCommand(["gnt-instance", "shutdown", name], fail=True)
486
  AssertCommand(["gnt-instance", "shutdown", "--force", name])
487
  AssertCommand(["gnt-instance", "start", name], fail=True)
488
  AssertCommand(["gnt-instance", "start", "--force", name], fail=True)
489

    
490
  # Also do offline to offline
491
  AssertCommand(["gnt-instance", "modify", "--offline", name])
492

    
493
  # And online again
494
  AssertCommand(["gnt-instance", "modify", "--online", name])
495

    
496

    
497
@InstanceCheck(INST_DOWN, INST_DOWN, FIRST_ARG)
498
def TestInstanceConvertDiskToPlain(instance, inodes):
499
  """gnt-instance modify -t"""
500
  name = instance["name"]
501
  template = qa_config.GetInstanceTemplate(instance)
502
  if template != "drbd":
503
    print qa_utils.FormatInfo("Unsupported template %s, skipping conversion"
504
                              " test" % template)
505
    return
506
  assert len(inodes) == 2
507
  AssertCommand(["gnt-instance", "modify", "-t", "plain", name])
508
  AssertCommand(["gnt-instance", "modify", "-t", "drbd",
509
                 "-n", inodes[1]["primary"], name])
510

    
511

    
512
@InstanceCheck(INST_DOWN, INST_DOWN, FIRST_ARG)
513
def TestInstanceGrowDisk(instance):
514
  """gnt-instance grow-disk"""
515
  if qa_config.GetExclusiveStorage():
516
    print qa_utils.FormatInfo("Test not supported with exclusive_storage")
517
    return
518
  name = instance["name"]
519
  all_size = qa_config.get("disk")
520
  all_grow = qa_config.get("disk-growth")
521
  if not all_grow:
522
    # missing disk sizes but instance grow disk has been enabled,
523
    # let's set fixed/nomimal growth
524
    all_grow = ["128M" for _ in all_size]
525
  for idx, (size, grow) in enumerate(zip(all_size, all_grow)):
526
    # succeed in grow by amount
527
    AssertCommand(["gnt-instance", "grow-disk", name, str(idx), grow])
528
    # fail in grow to the old size
529
    AssertCommand(["gnt-instance", "grow-disk", "--absolute", name, str(idx),
530
                   size], fail=True)
531
    # succeed to grow to old size + 2 * growth
532
    int_size = utils.ParseUnit(size)
533
    int_grow = utils.ParseUnit(grow)
534
    AssertCommand(["gnt-instance", "grow-disk", "--absolute", name, str(idx),
535
                   str(int_size + 2 * int_grow)])
536

    
537

    
538
def TestInstanceList():
539
  """gnt-instance list"""
540
  qa_utils.GenericQueryTest("gnt-instance", query.INSTANCE_FIELDS.keys())
541

    
542

    
543
def TestInstanceListFields():
544
  """gnt-instance list-fields"""
545
  qa_utils.GenericQueryFieldsTest("gnt-instance", query.INSTANCE_FIELDS.keys())
546

    
547

    
548
@InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
549
def TestInstanceConsole(instance):
550
  """gnt-instance console"""
551
  AssertCommand(["gnt-instance", "console", "--show-cmd", instance["name"]])
552

    
553

    
554
@InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
555
def TestReplaceDisks(instance, curr_nodes, other_nodes):
556
  """gnt-instance replace-disks"""
557
  def buildcmd(args):
558
    cmd = ["gnt-instance", "replace-disks"]
559
    cmd.extend(args)
560
    cmd.append(instance["name"])
561
    return cmd
562

    
563
  if not IsDiskReplacingSupported(instance):
564
    print qa_utils.FormatInfo("Instance doesn't support disk replacing,"
565
                              " skipping test")
566
    return
567

    
568
  # Currently all supported templates have one primary and one secondary node
569
  assert len(curr_nodes) == 2
570
  snode = curr_nodes[1]
571
  assert len(other_nodes) == 1
572
  othernode = other_nodes[0]
573

    
574
  options = qa_config.get("options", {})
575
  use_ialloc = options.get("use-iallocators", True)
576
  for data in [
577
    ["-p"],
578
    ["-s"],
579
    # A placeholder; the actual command choice depends on use_ialloc
580
    None,
581
    # Restore the original secondary
582
    ["--new-secondary=%s" % snode["primary"]],
583
    ]:
584
    if data is None:
585
      if use_ialloc:
586
        data = ["-I", constants.DEFAULT_IALLOCATOR_SHORTCUT]
587
      else:
588
        data = ["--new-secondary=%s" % othernode["primary"]]
589
    AssertCommand(buildcmd(data))
590

    
591
  AssertCommand(buildcmd(["-a"]))
592
  AssertCommand(["gnt-instance", "stop", instance["name"]])
593
  AssertCommand(buildcmd(["-a"]), fail=True)
594
  AssertCommand(["gnt-instance", "activate-disks", instance["name"]])
595
  AssertCommand(["gnt-instance", "activate-disks", "--wait-for-sync",
596
                 instance["name"]])
597
  AssertCommand(buildcmd(["-a"]))
598
  AssertCommand(["gnt-instance", "start", instance["name"]])
599

    
600

    
601
def _AssertRecreateDisks(cmdargs, instance, fail=False, check=True,
602
                         destroy=True):
603
  """Execute gnt-instance recreate-disks and check the result
604

605
  @param cmdargs: Arguments (instance name excluded)
606
  @param instance: Instance to operate on
607
  @param fail: True if the command is expected to fail
608
  @param check: If True and fail is False, check that the disks work
609
  @prama destroy: If True, destroy the old disks first
610

611
  """
612
  if destroy:
613
    _DestroyInstanceVolumes(instance)
614
  AssertCommand((["gnt-instance", "recreate-disks"] + cmdargs +
615
                 [instance["name"]]), fail)
616
  if not fail and check:
617
    # Quick check that the disks are there
618
    AssertCommand(["gnt-instance", "activate-disks", instance["name"]])
619
    AssertCommand(["gnt-instance", "activate-disks", "--wait-for-sync",
620
                   instance["name"]])
621
    AssertCommand(["gnt-instance", "deactivate-disks", instance["name"]])
622

    
623

    
624
@InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
625
def TestRecreateDisks(instance, inodes, othernodes):
626
  """gnt-instance recreate-disks
627

628
  @param instance: Instance to work on
629
  @param inodes: List of the current nodes of the instance
630
  @param othernodes: list/tuple of nodes where to temporarily recreate disks
631

632
  """
633
  options = qa_config.get("options", {})
634
  use_ialloc = options.get("use-iallocators", True)
635
  other_seq = ":".join([n["primary"] for n in othernodes])
636
  orig_seq = ":".join([n["primary"] for n in inodes])
637
  # These fail because the instance is running
638
  _AssertRecreateDisks(["-n", other_seq], instance, fail=True, destroy=False)
639
  if use_ialloc:
640
    _AssertRecreateDisks(["-I", "hail"], instance, fail=True, destroy=False)
641
  else:
642
    _AssertRecreateDisks(["-n", other_seq], instance, fail=True, destroy=False)
643
  AssertCommand(["gnt-instance", "stop", instance["name"]])
644
  # Disks exist: this should fail
645
  _AssertRecreateDisks([], instance, fail=True, destroy=False)
646
  # Recreate disks in place
647
  _AssertRecreateDisks([], instance)
648
  # Move disks away
649
  if use_ialloc:
650
    _AssertRecreateDisks(["-I", "hail"], instance)
651
    # Move disks somewhere else
652
    _AssertRecreateDisks(["-I", constants.DEFAULT_IALLOCATOR_SHORTCUT],
653
                         instance)
654
  else:
655
    _AssertRecreateDisks(["-n", other_seq], instance)
656
  # Move disks back
657
  _AssertRecreateDisks(["-n", orig_seq], instance, check=False)
658
  # This and InstanceCheck decoration check that the disks are working
659
  AssertCommand(["gnt-instance", "reinstall", "-f", instance["name"]])
660
  AssertCommand(["gnt-instance", "start", instance["name"]])
661

    
662

    
663
@InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
664
def TestInstanceExport(instance, node):
665
  """gnt-backup export -n ..."""
666
  name = instance["name"]
667
  AssertCommand(["gnt-backup", "export", "-n", node["primary"], name])
668
  return qa_utils.ResolveInstanceName(name)
669

    
670

    
671
@InstanceCheck(None, INST_DOWN, FIRST_ARG)
672
def TestInstanceExportWithRemove(instance, node):
673
  """gnt-backup export --remove-instance"""
674
  AssertCommand(["gnt-backup", "export", "-n", node["primary"],
675
                 "--remove-instance", instance["name"]])
676

    
677

    
678
@InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
679
def TestInstanceExportNoTarget(instance):
680
  """gnt-backup export (without target node, should fail)"""
681
  AssertCommand(["gnt-backup", "export", instance["name"]], fail=True)
682

    
683

    
684
@InstanceCheck(None, INST_DOWN, FIRST_ARG)
685
def TestInstanceImport(newinst, node, expnode, name):
686
  """gnt-backup import"""
687
  templ = constants.DT_PLAIN
688
  cmd = (["gnt-backup", "import",
689
          "--disk-template=%s" % templ,
690
          "--no-ip-check",
691
          "--src-node=%s" % expnode["primary"],
692
          "--src-dir=%s/%s" % (pathutils.EXPORT_DIR, name),
693
          "--node=%s" % node["primary"]] +
694
         _GetGenericAddParameters(newinst, force_mac=constants.VALUE_GENERATE))
695
  cmd.append(newinst["name"])
696
  AssertCommand(cmd)
697
  qa_config.SetInstanceTemplate(newinst, templ)
698

    
699

    
700
def TestBackupList(expnode):
701
  """gnt-backup list"""
702
  AssertCommand(["gnt-backup", "list", "--node=%s" % expnode["primary"]])
703

    
704
  qa_utils.GenericQueryTest("gnt-backup", query.EXPORT_FIELDS.keys(),
705
                            namefield=None, test_unknown=False)
706

    
707

    
708
def TestBackupListFields():
709
  """gnt-backup list-fields"""
710
  qa_utils.GenericQueryFieldsTest("gnt-backup", query.EXPORT_FIELDS.keys())
711

    
712

    
713
def TestRemoveInstanceOfflineNode(instance, snode, set_offline, set_online):
714
  """gtn-instance remove with an off-line node
715

716
  @param instance: instance
717
  @param snode: secondary node, to be set offline
718
  @param set_offline: function to call to set the node off-line
719
  @param set_online: function to call to set the node on-line
720

721
  """
722
  info = _GetInstanceInfo(instance["name"])
723
  set_offline(snode)
724
  try:
725
    TestInstanceRemove(instance)
726
  finally:
727
    set_online(snode)
728
  # Clean up the disks on the offline node
729
  for minor in info["drbd-minors"][snode["primary"]]:
730
    AssertCommand(["drbdsetup", str(minor), "down"], node=snode)
731
  AssertCommand(["lvremove", "-f"] + info["volumes"], node=snode)