Merge branch 'devel-2.7'
[ganeti-local] / qa / qa_instance.py
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, disk_template, 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
53   if disk_template != constants.DT_DISKLESS:
54     for idx, size in enumerate(qa_config.get("disk")):
55       params.extend(["--disk", "%s:size=%s" % (idx, size)])
56
57   # Set static MAC address if configured
58   if force_mac:
59     nic0_mac = force_mac
60   else:
61     nic0_mac = inst.GetNicMacAddr(0, None)
62
63   if nic0_mac:
64     params.extend(["--net", "0:mac=%s" % nic0_mac])
65
66   return params
67
68
69 def _DiskTest(node, disk_template, fail=False):
70   instance = qa_config.AcquireInstance()
71   try:
72     cmd = (["gnt-instance", "add",
73             "--os-type=%s" % qa_config.get("os"),
74             "--disk-template=%s" % disk_template,
75             "--node=%s" % node] +
76            _GetGenericAddParameters(instance, disk_template))
77     cmd.append(instance.name)
78
79     AssertCommand(cmd, fail=fail)
80
81     if not fail:
82       _CheckSsconfInstanceList(instance.name)
83       instance.SetDiskTemplate(disk_template)
84
85       return instance
86   except:
87     instance.Release()
88     raise
89
90   # Handle the case where creation is expected to fail
91   assert fail
92   instance.Release()
93   return None
94
95
96 def _GetInstanceInfo(instance):
97   """Return information about the actual state of an instance.
98
99   @type instance: string
100   @param instance: the instance name
101   @return: a dictionary with the following keys:
102       - "nodes": instance nodes, a list of strings
103       - "volumes": instance volume IDs, a list of strings
104       - "drbd-minors": DRBD minors used by the instance, a dictionary where
105         keys are nodes, and values are lists of integers (or an empty
106         dictionary for non-DRBD instances)
107
108   """
109   master = qa_config.GetMasterNode()
110   infocmd = utils.ShellQuoteArgs(["gnt-instance", "info", instance])
111   info_out = qa_utils.GetCommandOutput(master.primary, infocmd)
112   re_node = re.compile(r"^\s+-\s+(?:primary|secondaries):\s+(\S.+)$")
113   node_elem = r"([^,()]+)(?:\s+\([^)]+\))?"
114   # re_nodelist matches a list of nodes returned by gnt-instance info, e.g.:
115   #  node1.fqdn
116   #  node2.fqdn,node3.fqdn
117   #  node4.fqdn (group mygroup, group UUID 01234567-abcd-0123-4567-0123456789ab)
118   # FIXME This works with no more than 2 secondaries
119   re_nodelist = re.compile(node_elem + "(?:," + node_elem + ")?$")
120   re_vol = re.compile(r"^\s+logical_id:\s+(\S+)$")
121   re_drbdnode = re.compile(r"^\s+node[AB]:\s+([^\s,]+),\s+minor=([0-9]+)$")
122   nodes = []
123   vols = []
124   drbd_min = {}
125   for line in info_out.splitlines():
126     m = re_node.match(line)
127     if m:
128       nodestr = m.group(1)
129       m2 = re_nodelist.match(nodestr)
130       if m2:
131         nodes.extend(filter(None, m2.groups()))
132       else:
133         nodes.append(nodestr)
134     m = re_vol.match(line)
135     if m:
136       vols.append(m.group(1))
137     m = re_drbdnode.match(line)
138     if m:
139       node = m.group(1)
140       minor = int(m.group(2))
141       if drbd_min.get(node) is not None:
142         drbd_min[node].append(minor)
143       else:
144         drbd_min[node] = [minor]
145
146   assert nodes
147   assert len(nodes) < 2 or vols
148
149   return {
150     "nodes": nodes,
151     "volumes": vols,
152     "drbd-minors": drbd_min,
153     }
154
155
156 def _DestroyInstanceVolumes(instance):
157   """Remove all the LVM volumes of an instance.
158
159   This is used to simulate HW errors (dead nodes, broken disks...); the
160   configuration of the instance is not affected.
161   @type instance: dictionary
162   @param instance: the instance
163
164   """
165   info = _GetInstanceInfo(instance.name)
166   vols = info["volumes"]
167   for node in info["nodes"]:
168     AssertCommand(["lvremove", "-f"] + vols, node=node)
169
170
171 def _GetInstanceField(instance, field):
172   """Get the value of a field of an instance.
173
174   @type instance: string
175   @param instance: Instance name
176   @type field: string
177   @param field: Name of the field
178   @rtype: string
179
180   """
181   master = qa_config.GetMasterNode()
182   infocmd = utils.ShellQuoteArgs(["gnt-instance", "list", "--no-headers",
183                                   "--units", "m", "-o", field, instance])
184   return qa_utils.GetCommandOutput(master.primary, infocmd).strip()
185
186
187 def _GetBoolInstanceField(instance, field):
188   """Get the Boolean value of a field of an instance.
189
190   @type instance: string
191   @param instance: Instance name
192   @type field: string
193   @param field: Name of the field
194   @rtype: bool
195
196   """
197   info_out = _GetInstanceField(instance, field)
198   if info_out == "Y":
199     return True
200   elif info_out == "N":
201     return False
202   else:
203     raise qa_error.Error("Field %s of instance %s has a non-Boolean value:"
204                          " %s" % (field, instance, info_out))
205
206
207 def _GetNumInstanceField(instance, field):
208   """Get a numeric value of a field of an instance.
209
210   @type instance: string
211   @param instance: Instance name
212   @type field: string
213   @param field: Name of the field
214   @rtype: int or float
215
216   """
217   info_out = _GetInstanceField(instance, field)
218   try:
219     ret = int(info_out)
220   except ValueError:
221     try:
222       ret = float(info_out)
223     except ValueError:
224       raise qa_error.Error("Field %s of instance %s has a non-numeric value:"
225                            " %s" % (field, instance, info_out))
226   return ret
227
228
229 def GetInstanceSpec(instance, spec):
230   """Return the current spec for the given parameter.
231
232   @type instance: string
233   @param instance: Instance name
234   @type spec: string
235   @param spec: one of the supported parameters: "mem-size", "cpu-count",
236       "disk-count", "disk-size", "nic-count"
237   @rtype: tuple
238   @return: (minspec, maxspec); minspec and maxspec can be different only for
239       memory and disk size
240
241   """
242   specmap = {
243     "mem-size": ["be/minmem", "be/maxmem"],
244     "cpu-count": ["vcpus"],
245     "disk-count": ["disk.count"],
246     "disk-size": ["disk.size/ "],
247     "nic-count": ["nic.count"],
248     }
249   # For disks, first we need the number of disks
250   if spec == "disk-size":
251     (numdisk, _) = GetInstanceSpec(instance, "disk-count")
252     fields = ["disk.size/%s" % k for k in range(0, numdisk)]
253   else:
254     assert spec in specmap, "%s not in %s" % (spec, specmap)
255     fields = specmap[spec]
256   values = [_GetNumInstanceField(instance, f) for f in fields]
257   return (min(values), max(values))
258
259
260 def IsFailoverSupported(instance):
261   return instance.disk_template in constants.DTS_MIRRORED
262
263
264 def IsMigrationSupported(instance):
265   return instance.disk_template in constants.DTS_MIRRORED
266
267
268 def IsDiskReplacingSupported(instance):
269   return instance.disk_template == constants.DT_DRBD8
270
271
272 def TestInstanceAddWithPlainDisk(nodes, fail=False):
273   """gnt-instance add -t plain"""
274   assert len(nodes) == 1
275   instance = _DiskTest(nodes[0].primary, constants.DT_PLAIN, fail=fail)
276   if not fail:
277     qa_utils.RunInstanceCheck(instance, True)
278   return instance
279
280
281 @InstanceCheck(None, INST_UP, RETURN_VALUE)
282 def TestInstanceAddWithDrbdDisk(nodes):
283   """gnt-instance add -t drbd"""
284   assert len(nodes) == 2
285   return _DiskTest(":".join(map(operator.attrgetter("primary"), nodes)),
286                    constants.DT_DRBD8)
287
288
289 @InstanceCheck(None, INST_UP, RETURN_VALUE)
290 def TestInstanceAddDiskless(nodes):
291   """gnt-instance add -t diskless"""
292   assert len(nodes) == 1
293   return _DiskTest(nodes[0].primary, constants.DT_DISKLESS)
294
295
296 @InstanceCheck(None, INST_DOWN, FIRST_ARG)
297 def TestInstanceRemove(instance):
298   """gnt-instance remove"""
299   AssertCommand(["gnt-instance", "remove", "-f", instance.name])
300
301
302 @InstanceCheck(INST_DOWN, INST_UP, FIRST_ARG)
303 def TestInstanceStartup(instance):
304   """gnt-instance startup"""
305   AssertCommand(["gnt-instance", "startup", instance.name])
306
307
308 @InstanceCheck(INST_UP, INST_DOWN, FIRST_ARG)
309 def TestInstanceShutdown(instance):
310   """gnt-instance shutdown"""
311   AssertCommand(["gnt-instance", "shutdown", instance.name])
312
313
314 @InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
315 def TestInstanceReboot(instance):
316   """gnt-instance reboot"""
317   options = qa_config.get("options", {})
318   reboot_types = options.get("reboot-types", constants.REBOOT_TYPES)
319   name = instance.name
320   for rtype in reboot_types:
321     AssertCommand(["gnt-instance", "reboot", "--type=%s" % rtype, name])
322
323   AssertCommand(["gnt-instance", "shutdown", name])
324   qa_utils.RunInstanceCheck(instance, False)
325   AssertCommand(["gnt-instance", "reboot", name])
326
327   master = qa_config.GetMasterNode()
328   cmd = ["gnt-instance", "list", "--no-headers", "-o", "status", name]
329   result_output = qa_utils.GetCommandOutput(master.primary,
330                                             utils.ShellQuoteArgs(cmd))
331   AssertEqual(result_output.strip(), constants.INSTST_RUNNING)
332
333
334 @InstanceCheck(INST_DOWN, INST_DOWN, FIRST_ARG)
335 def TestInstanceReinstall(instance):
336   """gnt-instance reinstall"""
337   if instance.disk_template == constants.DT_DISKLESS:
338     print qa_utils.FormatInfo("Test not supported for diskless instances")
339     return
340
341   AssertCommand(["gnt-instance", "reinstall", "-f", instance.name])
342
343   # Test with non-existant OS definition
344   AssertCommand(["gnt-instance", "reinstall", "-f",
345                  "--os-type=NonExistantOsForQa",
346                  instance.name],
347                 fail=True)
348
349
350 def _ReadSsconfInstanceList():
351   """Reads ssconf_instance_list from the master node.
352
353   """
354   master = qa_config.GetMasterNode()
355
356   ssconf_path = utils.PathJoin(pathutils.DATA_DIR,
357                                "ssconf_%s" % constants.SS_INSTANCE_LIST)
358
359   cmd = ["cat", qa_utils.MakeNodePath(master, ssconf_path)]
360
361   return qa_utils.GetCommandOutput(master.primary,
362                                    utils.ShellQuoteArgs(cmd)).splitlines()
363
364
365 def _CheckSsconfInstanceList(instance):
366   """Checks if a certain instance is in the ssconf instance list.
367
368   @type instance: string
369   @param instance: Instance name
370
371   """
372   AssertIn(qa_utils.ResolveInstanceName(instance),
373            _ReadSsconfInstanceList())
374
375
376 @InstanceCheck(INST_DOWN, INST_DOWN, FIRST_ARG)
377 def TestInstanceRenameAndBack(rename_source, rename_target):
378   """gnt-instance rename
379
380   This must leave the instance with the original name, not the target
381   name.
382
383   """
384   _CheckSsconfInstanceList(rename_source)
385
386   # first do a rename to a different actual name, expecting it to fail
387   qa_utils.AddToEtcHosts(["meeeeh-not-exists", rename_target])
388   try:
389     AssertCommand(["gnt-instance", "rename", rename_source, rename_target],
390                   fail=True)
391     _CheckSsconfInstanceList(rename_source)
392   finally:
393     qa_utils.RemoveFromEtcHosts(["meeeeh-not-exists", rename_target])
394
395   # Check instance volume tags correctly updated
396   # FIXME: this is LVM specific!
397   info = _GetInstanceInfo(rename_source)
398   tags_cmd = ("lvs -o tags --noheadings %s | grep " %
399               (" ".join(info["volumes"]), ))
400
401   # and now rename instance to rename_target...
402   AssertCommand(["gnt-instance", "rename", rename_source, rename_target])
403   _CheckSsconfInstanceList(rename_target)
404   qa_utils.RunInstanceCheck(rename_source, False)
405   qa_utils.RunInstanceCheck(rename_target, False)
406
407   # NOTE: tags might not be the exactly as the instance name, due to
408   # charset restrictions; hence the test might be flaky
409   if rename_source != rename_target:
410     for node in info["nodes"]:
411       AssertCommand(tags_cmd + rename_source, node=node, fail=True)
412       AssertCommand(tags_cmd + rename_target, node=node, fail=False)
413
414   # and back
415   AssertCommand(["gnt-instance", "rename", rename_target, rename_source])
416   _CheckSsconfInstanceList(rename_source)
417   qa_utils.RunInstanceCheck(rename_target, False)
418
419   if rename_source != rename_target:
420     for node in info["nodes"]:
421       AssertCommand(tags_cmd + rename_source, node=node, fail=False)
422       AssertCommand(tags_cmd + rename_target, node=node, fail=True)
423
424
425 @InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
426 def TestInstanceFailover(instance):
427   """gnt-instance failover"""
428   if not IsFailoverSupported(instance):
429     print qa_utils.FormatInfo("Instance doesn't support failover, skipping"
430                               " test")
431     return
432
433   cmd = ["gnt-instance", "failover", "--force", instance.name]
434
435   # failover ...
436   AssertCommand(cmd)
437   qa_utils.RunInstanceCheck(instance, True)
438
439   # ... and back
440   AssertCommand(cmd)
441
442
443 @InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
444 def TestInstanceMigrate(instance, toggle_always_failover=True):
445   """gnt-instance migrate"""
446   if not IsMigrationSupported(instance):
447     print qa_utils.FormatInfo("Instance doesn't support migration, skipping"
448                               " test")
449     return
450
451   cmd = ["gnt-instance", "migrate", "--force", instance.name]
452   af_par = constants.BE_ALWAYS_FAILOVER
453   af_field = "be/" + constants.BE_ALWAYS_FAILOVER
454   af_init_val = _GetBoolInstanceField(instance.name, af_field)
455
456   # migrate ...
457   AssertCommand(cmd)
458   # TODO: Verify the choice between failover and migration
459   qa_utils.RunInstanceCheck(instance, True)
460
461   # ... and back (possibly with always_failover toggled)
462   if toggle_always_failover:
463     AssertCommand(["gnt-instance", "modify", "-B",
464                    ("%s=%s" % (af_par, not af_init_val)),
465                    instance.name])
466   AssertCommand(cmd)
467   # TODO: Verify the choice between failover and migration
468   qa_utils.RunInstanceCheck(instance, True)
469   if toggle_always_failover:
470     AssertCommand(["gnt-instance", "modify", "-B",
471                    ("%s=%s" % (af_par, af_init_val)), instance.name])
472
473   # TODO: Split into multiple tests
474   AssertCommand(["gnt-instance", "shutdown", instance.name])
475   qa_utils.RunInstanceCheck(instance, False)
476   AssertCommand(cmd, fail=True)
477   AssertCommand(["gnt-instance", "migrate", "--force", "--allow-failover",
478                  instance.name])
479   AssertCommand(["gnt-instance", "start", instance.name])
480   AssertCommand(cmd)
481   # @InstanceCheck enforces the check that the instance is running
482   qa_utils.RunInstanceCheck(instance, True)
483
484   AssertCommand(["gnt-instance", "modify", "-B",
485                  ("%s=%s" %
486                   (constants.BE_ALWAYS_FAILOVER, constants.VALUE_TRUE)),
487                  instance.name])
488
489   AssertCommand(cmd)
490   qa_utils.RunInstanceCheck(instance, True)
491   # TODO: Verify that a failover has been done instead of a migration
492
493   # TODO: Verify whether the default value is restored here (not hardcoded)
494   AssertCommand(["gnt-instance", "modify", "-B",
495                  ("%s=%s" %
496                   (constants.BE_ALWAYS_FAILOVER, constants.VALUE_FALSE)),
497                  instance.name])
498
499   AssertCommand(cmd)
500   qa_utils.RunInstanceCheck(instance, True)
501
502
503 def TestInstanceInfo(instance):
504   """gnt-instance info"""
505   AssertCommand(["gnt-instance", "info", instance.name])
506
507
508 @InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
509 def TestInstanceModify(instance):
510   """gnt-instance modify"""
511   default_hv = qa_config.GetDefaultHypervisor()
512
513   # Assume /sbin/init exists on all systems
514   test_kernel = "/sbin/init"
515   test_initrd = test_kernel
516
517   orig_maxmem = qa_config.get(constants.BE_MAXMEM)
518   orig_minmem = qa_config.get(constants.BE_MINMEM)
519   #orig_bridge = qa_config.get("bridge", "xen-br0")
520
521   args = [
522     ["-B", "%s=128" % constants.BE_MINMEM],
523     ["-B", "%s=128" % constants.BE_MAXMEM],
524     ["-B", "%s=%s,%s=%s" % (constants.BE_MINMEM, orig_minmem,
525                             constants.BE_MAXMEM, orig_maxmem)],
526     ["-B", "%s=2" % constants.BE_VCPUS],
527     ["-B", "%s=1" % constants.BE_VCPUS],
528     ["-B", "%s=%s" % (constants.BE_VCPUS, constants.VALUE_DEFAULT)],
529     ["-B", "%s=%s" % (constants.BE_ALWAYS_FAILOVER, constants.VALUE_TRUE)],
530     ["-B", "%s=%s" % (constants.BE_ALWAYS_FAILOVER, constants.VALUE_DEFAULT)],
531
532     ["-H", "%s=%s" % (constants.HV_KERNEL_PATH, test_kernel)],
533     ["-H", "%s=%s" % (constants.HV_KERNEL_PATH, constants.VALUE_DEFAULT)],
534
535     # TODO: bridge tests
536     #["--bridge", "xen-br1"],
537     #["--bridge", orig_bridge],
538     ]
539
540   if default_hv == constants.HT_XEN_PVM:
541     args.extend([
542       ["-H", "%s=%s" % (constants.HV_INITRD_PATH, test_initrd)],
543       ["-H", "no_%s" % (constants.HV_INITRD_PATH, )],
544       ["-H", "%s=%s" % (constants.HV_INITRD_PATH, constants.VALUE_DEFAULT)],
545       ])
546   elif default_hv == constants.HT_XEN_HVM:
547     args.extend([
548       ["-H", "%s=acn" % constants.HV_BOOT_ORDER],
549       ["-H", "%s=%s" % (constants.HV_BOOT_ORDER, constants.VALUE_DEFAULT)],
550       ])
551
552   for alist in args:
553     AssertCommand(["gnt-instance", "modify"] + alist + [instance.name])
554
555   # check no-modify
556   AssertCommand(["gnt-instance", "modify", instance.name], fail=True)
557
558   # Marking offline while instance is running must fail...
559   AssertCommand(["gnt-instance", "modify", "--offline", instance.name],
560                  fail=True)
561
562   # ...while making it online is ok, and should work
563   AssertCommand(["gnt-instance", "modify", "--online", instance.name])
564
565
566 @InstanceCheck(INST_DOWN, INST_DOWN, FIRST_ARG)
567 def TestInstanceStoppedModify(instance):
568   """gnt-instance modify (stopped instance)"""
569   name = instance.name
570
571   # Instance was not marked offline; try marking it online once more
572   AssertCommand(["gnt-instance", "modify", "--online", name])
573
574   # Mark instance as offline
575   AssertCommand(["gnt-instance", "modify", "--offline", name])
576
577   # When the instance is offline shutdown should only work with --force,
578   # while start should never work
579   AssertCommand(["gnt-instance", "shutdown", name], fail=True)
580   AssertCommand(["gnt-instance", "shutdown", "--force", name])
581   AssertCommand(["gnt-instance", "start", name], fail=True)
582   AssertCommand(["gnt-instance", "start", "--force", name], fail=True)
583
584   # Also do offline to offline
585   AssertCommand(["gnt-instance", "modify", "--offline", name])
586
587   # And online again
588   AssertCommand(["gnt-instance", "modify", "--online", name])
589
590
591 @InstanceCheck(INST_DOWN, INST_DOWN, FIRST_ARG)
592 def TestInstanceConvertDiskToPlain(instance, inodes):
593   """gnt-instance modify -t"""
594   name = instance.name
595
596   template = instance.disk_template
597   if template != constants.DT_DRBD8:
598     print qa_utils.FormatInfo("Unsupported template %s, skipping conversion"
599                               " test" % template)
600     return
601
602   assert len(inodes) == 2
603   AssertCommand(["gnt-instance", "modify", "-t", constants.DT_PLAIN, name])
604   AssertCommand(["gnt-instance", "modify", "-t", constants.DT_DRBD8,
605                  "-n", inodes[1].primary, name])
606
607
608 @InstanceCheck(INST_DOWN, INST_DOWN, FIRST_ARG)
609 def TestInstanceGrowDisk(instance):
610   """gnt-instance grow-disk"""
611   if qa_config.GetExclusiveStorage():
612     print qa_utils.FormatInfo("Test not supported with exclusive_storage")
613     return
614
615   if instance.disk_template == constants.DT_DISKLESS:
616     print qa_utils.FormatInfo("Test not supported for diskless instances")
617     return
618
619   name = instance.name
620   all_size = qa_config.get("disk")
621   all_grow = qa_config.get("disk-growth")
622
623   if not all_grow:
624     # missing disk sizes but instance grow disk has been enabled,
625     # let's set fixed/nomimal growth
626     all_grow = ["128M" for _ in all_size]
627
628   for idx, (size, grow) in enumerate(zip(all_size, all_grow)):
629     # succeed in grow by amount
630     AssertCommand(["gnt-instance", "grow-disk", name, str(idx), grow])
631     # fail in grow to the old size
632     AssertCommand(["gnt-instance", "grow-disk", "--absolute", name, str(idx),
633                    size], fail=True)
634     # succeed to grow to old size + 2 * growth
635     int_size = utils.ParseUnit(size)
636     int_grow = utils.ParseUnit(grow)
637     AssertCommand(["gnt-instance", "grow-disk", "--absolute", name, str(idx),
638                    str(int_size + 2 * int_grow)])
639
640
641 def TestInstanceList():
642   """gnt-instance list"""
643   qa_utils.GenericQueryTest("gnt-instance", query.INSTANCE_FIELDS.keys())
644
645
646 def TestInstanceListFields():
647   """gnt-instance list-fields"""
648   qa_utils.GenericQueryFieldsTest("gnt-instance", query.INSTANCE_FIELDS.keys())
649
650
651 @InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
652 def TestInstanceConsole(instance):
653   """gnt-instance console"""
654   AssertCommand(["gnt-instance", "console", "--show-cmd", instance.name])
655
656
657 @InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
658 def TestReplaceDisks(instance, curr_nodes, other_nodes):
659   """gnt-instance replace-disks"""
660   def buildcmd(args):
661     cmd = ["gnt-instance", "replace-disks"]
662     cmd.extend(args)
663     cmd.append(instance.name)
664     return cmd
665
666   if not IsDiskReplacingSupported(instance):
667     print qa_utils.FormatInfo("Instance doesn't support disk replacing,"
668                               " skipping test")
669     return
670
671   # Currently all supported templates have one primary and one secondary node
672   assert len(curr_nodes) == 2
673   snode = curr_nodes[1]
674   assert len(other_nodes) == 1
675   othernode = other_nodes[0]
676
677   options = qa_config.get("options", {})
678   use_ialloc = options.get("use-iallocators", True)
679   for data in [
680     ["-p"],
681     ["-s"],
682     # A placeholder; the actual command choice depends on use_ialloc
683     None,
684     # Restore the original secondary
685     ["--new-secondary=%s" % snode.primary],
686     ]:
687     if data is None:
688       if use_ialloc:
689         data = ["-I", constants.DEFAULT_IALLOCATOR_SHORTCUT]
690       else:
691         data = ["--new-secondary=%s" % othernode.primary]
692     AssertCommand(buildcmd(data))
693
694   AssertCommand(buildcmd(["-a"]))
695   AssertCommand(["gnt-instance", "stop", instance.name])
696   AssertCommand(buildcmd(["-a"]), fail=True)
697   AssertCommand(["gnt-instance", "activate-disks", instance.name])
698   AssertCommand(["gnt-instance", "activate-disks", "--wait-for-sync",
699                  instance.name])
700   AssertCommand(buildcmd(["-a"]))
701   AssertCommand(["gnt-instance", "start", instance.name])
702
703
704 def _AssertRecreateDisks(cmdargs, instance, fail=False, check=True,
705                          destroy=True):
706   """Execute gnt-instance recreate-disks and check the result
707
708   @param cmdargs: Arguments (instance name excluded)
709   @param instance: Instance to operate on
710   @param fail: True if the command is expected to fail
711   @param check: If True and fail is False, check that the disks work
712   @prama destroy: If True, destroy the old disks first
713
714   """
715   if destroy:
716     _DestroyInstanceVolumes(instance)
717   AssertCommand((["gnt-instance", "recreate-disks"] + cmdargs +
718                  [instance.name]), fail)
719   if not fail and check:
720     # Quick check that the disks are there
721     AssertCommand(["gnt-instance", "activate-disks", instance.name])
722     AssertCommand(["gnt-instance", "activate-disks", "--wait-for-sync",
723                    instance.name])
724     AssertCommand(["gnt-instance", "deactivate-disks", instance.name])
725
726
727 @InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
728 def TestRecreateDisks(instance, inodes, othernodes):
729   """gnt-instance recreate-disks
730
731   @param instance: Instance to work on
732   @param inodes: List of the current nodes of the instance
733   @param othernodes: list/tuple of nodes where to temporarily recreate disks
734
735   """
736   options = qa_config.get("options", {})
737   use_ialloc = options.get("use-iallocators", True)
738   other_seq = ":".join([n.primary for n in othernodes])
739   orig_seq = ":".join([n.primary for n in inodes])
740   # These fail because the instance is running
741   _AssertRecreateDisks(["-n", other_seq], instance, fail=True, destroy=False)
742   if use_ialloc:
743     _AssertRecreateDisks(["-I", "hail"], instance, fail=True, destroy=False)
744   else:
745     _AssertRecreateDisks(["-n", other_seq], instance, fail=True, destroy=False)
746   AssertCommand(["gnt-instance", "stop", instance.name])
747   # Disks exist: this should fail
748   _AssertRecreateDisks([], instance, fail=True, destroy=False)
749   # Recreate disks in place
750   _AssertRecreateDisks([], instance)
751   # Move disks away
752   if use_ialloc:
753     _AssertRecreateDisks(["-I", "hail"], instance)
754     # Move disks somewhere else
755     _AssertRecreateDisks(["-I", constants.DEFAULT_IALLOCATOR_SHORTCUT],
756                          instance)
757   else:
758     _AssertRecreateDisks(["-n", other_seq], instance)
759   # Move disks back
760   _AssertRecreateDisks(["-n", orig_seq], instance, check=False)
761   # This and InstanceCheck decoration check that the disks are working
762   AssertCommand(["gnt-instance", "reinstall", "-f", instance.name])
763   AssertCommand(["gnt-instance", "start", instance.name])
764
765
766 @InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
767 def TestInstanceExport(instance, node):
768   """gnt-backup export -n ..."""
769   name = instance.name
770   AssertCommand(["gnt-backup", "export", "-n", node.primary, name])
771   return qa_utils.ResolveInstanceName(name)
772
773
774 @InstanceCheck(None, INST_DOWN, FIRST_ARG)
775 def TestInstanceExportWithRemove(instance, node):
776   """gnt-backup export --remove-instance"""
777   AssertCommand(["gnt-backup", "export", "-n", node.primary,
778                  "--remove-instance", instance.name])
779
780
781 @InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
782 def TestInstanceExportNoTarget(instance):
783   """gnt-backup export (without target node, should fail)"""
784   AssertCommand(["gnt-backup", "export", instance.name], fail=True)
785
786
787 @InstanceCheck(None, INST_DOWN, FIRST_ARG)
788 def TestInstanceImport(newinst, node, expnode, name):
789   """gnt-backup import"""
790   templ = constants.DT_PLAIN
791   cmd = (["gnt-backup", "import",
792           "--disk-template=%s" % templ,
793           "--no-ip-check",
794           "--src-node=%s" % expnode.primary,
795           "--src-dir=%s/%s" % (pathutils.EXPORT_DIR, name),
796           "--node=%s" % node.primary] +
797          _GetGenericAddParameters(newinst, templ,
798                                   force_mac=constants.VALUE_GENERATE))
799   cmd.append(newinst.name)
800   AssertCommand(cmd)
801   newinst.SetDiskTemplate(templ)
802
803
804 def TestBackupList(expnode):
805   """gnt-backup list"""
806   AssertCommand(["gnt-backup", "list", "--node=%s" % expnode.primary])
807
808   qa_utils.GenericQueryTest("gnt-backup", query.EXPORT_FIELDS.keys(),
809                             namefield=None, test_unknown=False)
810
811
812 def TestBackupListFields():
813   """gnt-backup list-fields"""
814   qa_utils.GenericQueryFieldsTest("gnt-backup", query.EXPORT_FIELDS.keys())
815
816
817 def TestRemoveInstanceOfflineNode(instance, snode, set_offline, set_online):
818   """gtn-instance remove with an off-line node
819
820   @param instance: instance
821   @param snode: secondary node, to be set offline
822   @param set_offline: function to call to set the node off-line
823   @param set_online: function to call to set the node on-line
824
825   """
826   info = _GetInstanceInfo(instance.name)
827   set_offline(snode)
828   try:
829     TestInstanceRemove(instance)
830   finally:
831     set_online(snode)
832   # Clean up the disks on the offline node
833   for minor in info["drbd-minors"][snode.primary]:
834     AssertCommand(["drbdsetup", str(minor), "down"], node=snode)
835   AssertCommand(["lvremove", "-f"] + info["volumes"], node=snode)