Statistics
| Branch: | Tag: | Revision:

root / qa / qa_instance.py @ 02a5fe0e

History | View | Annotate | Download (24.2 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 = inst.GetNicMacAddr(0, None)
60

    
61
  if nic0_mac:
62
    params.extend(["--net", "0:mac=%s" % nic0_mac])
63

    
64
  return params
65

    
66

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

    
77
    AssertCommand(cmd)
78

    
79
    _CheckSsconfInstanceList(instance.name)
80
    instance.SetDiskTemplate(disk_template)
81

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

    
87

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

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

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

    
141

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

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

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

    
156

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

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

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

    
178

    
179
def IsFailoverSupported(instance):
180
  return instance.disk_template in constants.DTS_MIRRORED
181

    
182

    
183
def IsMigrationSupported(instance):
184
  return instance.disk_template in constants.DTS_MIRRORED
185

    
186

    
187
def IsDiskReplacingSupported(instance):
188
  return instance.disk_template == constants.DT_DRBD8
189

    
190

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

    
197

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

    
205

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

    
211

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

    
217

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

    
223

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

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

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

    
243

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

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

    
255

    
256
def _ReadSsconfInstanceList():
257
  """Reads ssconf_instance_list from the master node.
258

259
  """
260
  master = qa_config.GetMasterNode()
261

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

    
265
  return qa_utils.GetCommandOutput(master.primary,
266
                                   utils.ShellQuoteArgs(cmd)).splitlines()
267

    
268

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

272
  @type instance: string
273
  @param instance: Instance name
274

275
  """
276
  AssertIn(qa_utils.ResolveInstanceName(instance),
277
           _ReadSsconfInstanceList())
278

    
279

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

284
  This must leave the instance with the original name, not the target
285
  name.
286

287
  """
288
  _CheckSsconfInstanceList(rename_source)
289

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

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

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

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

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

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

    
328

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

    
337
  cmd = ["gnt-instance", "failover", "--force", instance.name]
338

    
339
  # failover ...
340
  AssertCommand(cmd)
341
  qa_utils.RunInstanceCheck(instance, True)
342

    
343
  # ... and back
344
  AssertCommand(cmd)
345

    
346

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

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

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

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

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

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

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

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

    
403
  AssertCommand(cmd)
404
  qa_utils.RunInstanceCheck(instance, True)
405

    
406

    
407
def TestInstanceInfo(instance):
408
  """gnt-instance info"""
409
  AssertCommand(["gnt-instance", "info", instance.name])
410

    
411

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

    
417
  # Assume /sbin/init exists on all systems
418
  test_kernel = "/sbin/init"
419
  test_initrd = test_kernel
420

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

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

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

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

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

    
456
  for alist in args:
457
    AssertCommand(["gnt-instance", "modify"] + alist + [instance.name])
458

    
459
  # check no-modify
460
  AssertCommand(["gnt-instance", "modify", instance.name], fail=True)
461

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

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

    
469

    
470
@InstanceCheck(INST_DOWN, INST_DOWN, FIRST_ARG)
471
def TestInstanceStoppedModify(instance):
472
  """gnt-instance modify (stopped instance)"""
473
  name = instance.name
474

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

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

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

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

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

    
494

    
495
@InstanceCheck(INST_DOWN, INST_DOWN, FIRST_ARG)
496
def TestInstanceConvertDiskToPlain(instance, inodes):
497
  """gnt-instance modify -t"""
498
  name = instance.name
499

    
500
  template = instance.disk_template
501
  if template != constants.DT_DRBD8:
502
    print qa_utils.FormatInfo("Unsupported template %s, skipping conversion"
503
                              " test" % template)
504
    return
505

    
506
  assert len(inodes) == 2
507
  AssertCommand(["gnt-instance", "modify", "-t", constants.DT_PLAIN, name])
508
  AssertCommand(["gnt-instance", "modify", "-t", constants.DT_DRBD8,
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
  newinst.SetDiskTemplate(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)