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