QA for spindles in creating disks
[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 os
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 AssertCommand, AssertEqual
39 from qa_utils import InstanceCheck, INST_DOWN, INST_UP, FIRST_ARG, RETURN_VALUE
40 from qa_instance_utils import CheckSsconfInstanceList, \
41                               CreateInstanceDrbd8, \
42                               CreateInstanceByDiskTemplate, \
43                               CreateInstanceByDiskTemplateOneNode, \
44                               GetGenericAddParameters
45
46
47 def _GetDiskStatePath(disk):
48   return "/sys/block/%s/device/state" % disk
49
50
51 def _GetInstanceInfo(instance):
52   """Return information about the actual state of an instance.
53
54   @type instance: string
55   @param instance: the instance name
56   @return: a dictionary with the following keys:
57       - "nodes": instance nodes, a list of strings
58       - "volumes": instance volume IDs, a list of strings
59       - "drbd-minors": DRBD minors used by the instance, a dictionary where
60         keys are nodes, and values are lists of integers (or an empty
61         dictionary for non-DRBD instances)
62       - "disk-template": instance disk template
63       - "storage-type": storage type associated with the instance disk template
64
65   """
66   node_elem = r"([^,()]+)(?:\s+\([^)]+\))?"
67   # re_nodelist matches a list of nodes returned by gnt-instance info, e.g.:
68   #  node1.fqdn
69   #  node2.fqdn,node3.fqdn
70   #  node4.fqdn (group mygroup, group UUID 01234567-abcd-0123-4567-0123456789ab)
71   # FIXME This works with no more than 2 secondaries
72   re_nodelist = re.compile(node_elem + "(?:," + node_elem + ")?$")
73
74   info = qa_utils.GetObjectInfo(["gnt-instance", "info", instance])[0]
75   nodes = []
76   for nodeinfo in info["Nodes"]:
77     if "primary" in nodeinfo:
78       nodes.append(nodeinfo["primary"])
79     elif "secondaries" in nodeinfo:
80       nodestr = nodeinfo["secondaries"]
81       if nodestr:
82         m = re_nodelist.match(nodestr)
83         if m:
84           nodes.extend(filter(None, m.groups()))
85         else:
86           nodes.append(nodestr)
87
88   disk_template = info["Disk template"]
89   if not disk_template:
90     raise qa_error.Error("Can't get instance disk template")
91   storage_type = constants.DISK_TEMPLATES_STORAGE_TYPE[disk_template]
92
93   re_drbdnode = re.compile(r"^([^\s,]+),\s+minor=([0-9]+)$")
94   vols = []
95   drbd_min = {}
96   for (count, diskinfo) in enumerate(info["Disks"]):
97     (dtype, _) = diskinfo["disk/%s" % count].split(",", 1)
98     if dtype == constants.LD_DRBD8:
99       for child in diskinfo["child devices"]:
100         vols.append(child["logical_id"])
101       for key in ["nodeA", "nodeB"]:
102         m = re_drbdnode.match(diskinfo[key])
103         if not m:
104           raise qa_error.Error("Cannot parse DRBD info: %s" % diskinfo[key])
105         node = m.group(1)
106         minor = int(m.group(2))
107         minorlist = drbd_min.setdefault(node, [])
108         minorlist.append(minor)
109     elif dtype == constants.LD_LV:
110       vols.append(diskinfo["logical_id"])
111
112   assert nodes
113   assert len(nodes) < 2 or vols
114   return {
115     "nodes": nodes,
116     "volumes": vols,
117     "drbd-minors": drbd_min,
118     "disk-template": disk_template,
119     "storage-type": storage_type,
120     }
121
122
123 def _DestroyInstanceDisks(instance):
124   """Remove all the backend disks of an instance.
125
126   This is used to simulate HW errors (dead nodes, broken disks...); the
127   configuration of the instance is not affected.
128   @type instance: dictionary
129   @param instance: the instance
130
131   """
132   info = _GetInstanceInfo(instance.name)
133   # FIXME: destruction/removal should be part of the disk class
134   if info["storage-type"] == constants.ST_LVM_VG:
135     vols = info["volumes"]
136     for node in info["nodes"]:
137       AssertCommand(["lvremove", "-f"] + vols, node=node)
138   elif info["storage-type"] == constants.ST_FILE:
139     # FIXME: file storage dir not configurable in qa
140     # Note that this works for both file and sharedfile, and this is intended.
141     filestorage = pathutils.DEFAULT_FILE_STORAGE_DIR
142     idir = os.path.join(filestorage, instance.name)
143     for node in info["nodes"]:
144       AssertCommand(["rm", "-rf", idir], node=node)
145   elif info["storage-type"] == constants.ST_DISKLESS:
146     pass
147
148
149 def _GetInstanceField(instance, field):
150   """Get the value of a field of an instance.
151
152   @type instance: string
153   @param instance: Instance name
154   @type field: string
155   @param field: Name of the field
156   @rtype: string
157
158   """
159   master = qa_config.GetMasterNode()
160   infocmd = utils.ShellQuoteArgs(["gnt-instance", "list", "--no-headers",
161                                   "--units", "m", "-o", field, instance])
162   return qa_utils.GetCommandOutput(master.primary, infocmd).strip()
163
164
165 def _GetBoolInstanceField(instance, field):
166   """Get the Boolean value of a field of an instance.
167
168   @type instance: string
169   @param instance: Instance name
170   @type field: string
171   @param field: Name of the field
172   @rtype: bool
173
174   """
175   info_out = _GetInstanceField(instance, field)
176   if info_out == "Y":
177     return True
178   elif info_out == "N":
179     return False
180   else:
181     raise qa_error.Error("Field %s of instance %s has a non-Boolean value:"
182                          " %s" % (field, instance, info_out))
183
184
185 def _GetNumInstanceField(instance, field):
186   """Get a numeric value of a field of an instance.
187
188   @type instance: string
189   @param instance: Instance name
190   @type field: string
191   @param field: Name of the field
192   @rtype: int or float
193
194   """
195   info_out = _GetInstanceField(instance, field)
196   try:
197     ret = int(info_out)
198   except ValueError:
199     try:
200       ret = float(info_out)
201     except ValueError:
202       raise qa_error.Error("Field %s of instance %s has a non-numeric value:"
203                            " %s" % (field, instance, info_out))
204   return ret
205
206
207 def GetInstanceSpec(instance, spec):
208   """Return the current spec for the given parameter.
209
210   @type instance: string
211   @param instance: Instance name
212   @type spec: string
213   @param spec: one of the supported parameters: "memory-size", "cpu-count",
214       "disk-count", "disk-size", "nic-count"
215   @rtype: tuple
216   @return: (minspec, maxspec); minspec and maxspec can be different only for
217       memory and disk size
218
219   """
220   specmap = {
221     "memory-size": ["be/minmem", "be/maxmem"],
222     "cpu-count": ["vcpus"],
223     "disk-count": ["disk.count"],
224     "disk-size": ["disk.size/ "],
225     "nic-count": ["nic.count"],
226     }
227   # For disks, first we need the number of disks
228   if spec == "disk-size":
229     (numdisk, _) = GetInstanceSpec(instance, "disk-count")
230     fields = ["disk.size/%s" % k for k in range(0, numdisk)]
231   else:
232     assert spec in specmap, "%s not in %s" % (spec, specmap)
233     fields = specmap[spec]
234   values = [_GetNumInstanceField(instance, f) for f in fields]
235   return (min(values), max(values))
236
237
238 def IsFailoverSupported(instance):
239   return instance.disk_template in constants.DTS_MIRRORED
240
241
242 def IsMigrationSupported(instance):
243   return instance.disk_template in constants.DTS_MIRRORED
244
245
246 def IsDiskReplacingSupported(instance):
247   return instance.disk_template == constants.DT_DRBD8
248
249
250 def IsDiskSupported(instance):
251   return instance.disk_template != constants.DT_DISKLESS
252
253
254 def TestInstanceAddWithPlainDisk(nodes, fail=False):
255   """gnt-instance add -t plain"""
256   if constants.DT_PLAIN in qa_config.GetEnabledDiskTemplates():
257     instance = CreateInstanceByDiskTemplateOneNode(nodes, constants.DT_PLAIN,
258                                                     fail=fail)
259     if not fail:
260       qa_utils.RunInstanceCheck(instance, True)
261     return instance
262
263
264 @InstanceCheck(None, INST_UP, RETURN_VALUE)
265 def TestInstanceAddWithDrbdDisk(nodes):
266   """gnt-instance add -t drbd"""
267   if constants.DT_DRBD8 in qa_config.GetEnabledDiskTemplates():
268     return CreateInstanceDrbd8(nodes)
269
270
271 @InstanceCheck(None, INST_UP, RETURN_VALUE)
272 def TestInstanceAddFile(nodes):
273   """gnt-instance add -t file"""
274   assert len(nodes) == 1
275   if constants.DT_FILE in qa_config.GetEnabledDiskTemplates():
276     return CreateInstanceByDiskTemplateOneNode(nodes, constants.DT_FILE)
277
278
279 @InstanceCheck(None, INST_UP, RETURN_VALUE)
280 def TestInstanceAddDiskless(nodes):
281   """gnt-instance add -t diskless"""
282   assert len(nodes) == 1
283   if constants.DT_FILE in qa_config.GetEnabledDiskTemplates():
284     return CreateInstanceByDiskTemplateOneNode(nodes, constants.DT_DISKLESS)
285
286
287 @InstanceCheck(None, INST_DOWN, FIRST_ARG)
288 def TestInstanceRemove(instance):
289   """gnt-instance remove"""
290   AssertCommand(["gnt-instance", "remove", "-f", instance.name])
291
292
293 @InstanceCheck(INST_DOWN, INST_UP, FIRST_ARG)
294 def TestInstanceStartup(instance):
295   """gnt-instance startup"""
296   AssertCommand(["gnt-instance", "startup", instance.name])
297
298
299 @InstanceCheck(INST_UP, INST_DOWN, FIRST_ARG)
300 def TestInstanceShutdown(instance):
301   """gnt-instance shutdown"""
302   AssertCommand(["gnt-instance", "shutdown", instance.name])
303
304
305 @InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
306 def TestInstanceReboot(instance):
307   """gnt-instance reboot"""
308   options = qa_config.get("options", {})
309   reboot_types = options.get("reboot-types", constants.REBOOT_TYPES)
310   name = instance.name
311   for rtype in reboot_types:
312     AssertCommand(["gnt-instance", "reboot", "--type=%s" % rtype, name])
313
314   AssertCommand(["gnt-instance", "shutdown", name])
315   qa_utils.RunInstanceCheck(instance, False)
316   AssertCommand(["gnt-instance", "reboot", name])
317
318   master = qa_config.GetMasterNode()
319   cmd = ["gnt-instance", "list", "--no-headers", "-o", "status", name]
320   result_output = qa_utils.GetCommandOutput(master.primary,
321                                             utils.ShellQuoteArgs(cmd))
322   AssertEqual(result_output.strip(), constants.INSTST_RUNNING)
323
324
325 @InstanceCheck(INST_DOWN, INST_DOWN, FIRST_ARG)
326 def TestInstanceReinstall(instance):
327   """gnt-instance reinstall"""
328   if instance.disk_template == constants.DT_DISKLESS:
329     print qa_utils.FormatInfo("Test not supported for diskless instances")
330     return
331
332   AssertCommand(["gnt-instance", "reinstall", "-f", instance.name])
333
334   # Test with non-existant OS definition
335   AssertCommand(["gnt-instance", "reinstall", "-f",
336                  "--os-type=NonExistantOsForQa",
337                  instance.name],
338                 fail=True)
339
340
341 @InstanceCheck(INST_DOWN, INST_DOWN, FIRST_ARG)
342 def TestInstanceRenameAndBack(rename_source, rename_target):
343   """gnt-instance rename
344
345   This must leave the instance with the original name, not the target
346   name.
347
348   """
349   CheckSsconfInstanceList(rename_source)
350
351   # first do a rename to a different actual name, expecting it to fail
352   qa_utils.AddToEtcHosts(["meeeeh-not-exists", rename_target])
353   try:
354     AssertCommand(["gnt-instance", "rename", rename_source, rename_target],
355                   fail=True)
356     CheckSsconfInstanceList(rename_source)
357   finally:
358     qa_utils.RemoveFromEtcHosts(["meeeeh-not-exists", rename_target])
359
360   info = _GetInstanceInfo(rename_source)
361
362   # Check instance volume tags correctly updated. Note that this check is lvm
363   # specific, so we skip it for non-lvm-based instances.
364   # FIXME: This will need updating when instances will be able to have
365   # different disks living on storage pools with etherogeneous storage types.
366   # FIXME: This check should be put inside the disk/storage class themselves,
367   # rather than explicitly called here.
368   if info["storage-type"] == constants.ST_LVM_VG:
369     # In the lvm world we can check for tags on the logical volume
370     tags_cmd = ("lvs -o tags --noheadings %s | grep " %
371                 (" ".join(info["volumes"]), ))
372   else:
373     # Other storage types don't have tags, so we use an always failing command,
374     # to make sure it never gets executed
375     tags_cmd = "false"
376
377   # and now rename instance to rename_target...
378   AssertCommand(["gnt-instance", "rename", rename_source, rename_target])
379   CheckSsconfInstanceList(rename_target)
380   qa_utils.RunInstanceCheck(rename_source, False)
381   qa_utils.RunInstanceCheck(rename_target, False)
382
383   # NOTE: tags might not be the exactly as the instance name, due to
384   # charset restrictions; hence the test might be flaky
385   if (rename_source != rename_target and
386       info["storage-type"] == constants.ST_LVM_VG):
387     for node in info["nodes"]:
388       AssertCommand(tags_cmd + rename_source, node=node, fail=True)
389       AssertCommand(tags_cmd + rename_target, node=node, fail=False)
390
391   # and back
392   AssertCommand(["gnt-instance", "rename", rename_target, rename_source])
393   CheckSsconfInstanceList(rename_source)
394   qa_utils.RunInstanceCheck(rename_target, False)
395
396   if (rename_source != rename_target and
397       info["storage-type"] == constants.ST_LVM_VG):
398     for node in info["nodes"]:
399       AssertCommand(tags_cmd + rename_source, node=node, fail=False)
400       AssertCommand(tags_cmd + rename_target, node=node, fail=True)
401
402
403 @InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
404 def TestInstanceFailover(instance):
405   """gnt-instance failover"""
406   if not IsFailoverSupported(instance):
407     print qa_utils.FormatInfo("Instance doesn't support failover, skipping"
408                               " test")
409     return
410
411   cmd = ["gnt-instance", "failover", "--force", instance.name]
412
413   # failover ...
414   AssertCommand(cmd)
415   qa_utils.RunInstanceCheck(instance, True)
416
417   # ... and back
418   AssertCommand(cmd)
419
420
421 @InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
422 def TestInstanceMigrate(instance, toggle_always_failover=True):
423   """gnt-instance migrate"""
424   if not IsMigrationSupported(instance):
425     print qa_utils.FormatInfo("Instance doesn't support migration, skipping"
426                               " test")
427     return
428
429   cmd = ["gnt-instance", "migrate", "--force", instance.name]
430   af_par = constants.BE_ALWAYS_FAILOVER
431   af_field = "be/" + constants.BE_ALWAYS_FAILOVER
432   af_init_val = _GetBoolInstanceField(instance.name, af_field)
433
434   # migrate ...
435   AssertCommand(cmd)
436   # TODO: Verify the choice between failover and migration
437   qa_utils.RunInstanceCheck(instance, True)
438
439   # ... and back (possibly with always_failover toggled)
440   if toggle_always_failover:
441     AssertCommand(["gnt-instance", "modify", "-B",
442                    ("%s=%s" % (af_par, not af_init_val)),
443                    instance.name])
444   AssertCommand(cmd)
445   # TODO: Verify the choice between failover and migration
446   qa_utils.RunInstanceCheck(instance, True)
447   if toggle_always_failover:
448     AssertCommand(["gnt-instance", "modify", "-B",
449                    ("%s=%s" % (af_par, af_init_val)), instance.name])
450
451   # TODO: Split into multiple tests
452   AssertCommand(["gnt-instance", "shutdown", instance.name])
453   qa_utils.RunInstanceCheck(instance, False)
454   AssertCommand(cmd, fail=True)
455   AssertCommand(["gnt-instance", "migrate", "--force", "--allow-failover",
456                  instance.name])
457   AssertCommand(["gnt-instance", "start", instance.name])
458   AssertCommand(cmd)
459   # @InstanceCheck enforces the check that the instance is running
460   qa_utils.RunInstanceCheck(instance, True)
461
462   AssertCommand(["gnt-instance", "modify", "-B",
463                  ("%s=%s" %
464                   (constants.BE_ALWAYS_FAILOVER, constants.VALUE_TRUE)),
465                  instance.name])
466
467   AssertCommand(cmd)
468   qa_utils.RunInstanceCheck(instance, True)
469   # TODO: Verify that a failover has been done instead of a migration
470
471   # TODO: Verify whether the default value is restored here (not hardcoded)
472   AssertCommand(["gnt-instance", "modify", "-B",
473                  ("%s=%s" %
474                   (constants.BE_ALWAYS_FAILOVER, constants.VALUE_FALSE)),
475                  instance.name])
476
477   AssertCommand(cmd)
478   qa_utils.RunInstanceCheck(instance, True)
479
480
481 def TestInstanceInfo(instance):
482   """gnt-instance info"""
483   AssertCommand(["gnt-instance", "info", instance.name])
484
485
486 @InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
487 def TestInstanceModify(instance):
488   """gnt-instance modify"""
489   default_hv = qa_config.GetDefaultHypervisor()
490
491   # Assume /sbin/init exists on all systems
492   test_kernel = "/sbin/init"
493   test_initrd = test_kernel
494
495   orig_maxmem = qa_config.get(constants.BE_MAXMEM)
496   orig_minmem = qa_config.get(constants.BE_MINMEM)
497   #orig_bridge = qa_config.get("bridge", "xen-br0")
498
499   args = [
500     ["-B", "%s=128" % constants.BE_MINMEM],
501     ["-B", "%s=128" % constants.BE_MAXMEM],
502     ["-B", "%s=%s,%s=%s" % (constants.BE_MINMEM, orig_minmem,
503                             constants.BE_MAXMEM, orig_maxmem)],
504     ["-B", "%s=2" % constants.BE_VCPUS],
505     ["-B", "%s=1" % constants.BE_VCPUS],
506     ["-B", "%s=%s" % (constants.BE_VCPUS, constants.VALUE_DEFAULT)],
507     ["-B", "%s=%s" % (constants.BE_ALWAYS_FAILOVER, constants.VALUE_TRUE)],
508     ["-B", "%s=%s" % (constants.BE_ALWAYS_FAILOVER, constants.VALUE_DEFAULT)],
509
510     ["-H", "%s=%s" % (constants.HV_KERNEL_PATH, test_kernel)],
511     ["-H", "%s=%s" % (constants.HV_KERNEL_PATH, constants.VALUE_DEFAULT)],
512
513     # TODO: bridge tests
514     #["--bridge", "xen-br1"],
515     #["--bridge", orig_bridge],
516     ]
517
518   if default_hv == constants.HT_XEN_PVM:
519     args.extend([
520       ["-H", "%s=%s" % (constants.HV_INITRD_PATH, test_initrd)],
521       ["-H", "no_%s" % (constants.HV_INITRD_PATH, )],
522       ["-H", "%s=%s" % (constants.HV_INITRD_PATH, constants.VALUE_DEFAULT)],
523       ])
524   elif default_hv == constants.HT_XEN_HVM:
525     args.extend([
526       ["-H", "%s=acn" % constants.HV_BOOT_ORDER],
527       ["-H", "%s=%s" % (constants.HV_BOOT_ORDER, constants.VALUE_DEFAULT)],
528       ])
529
530   for alist in args:
531     AssertCommand(["gnt-instance", "modify"] + alist + [instance.name])
532
533   # check no-modify
534   AssertCommand(["gnt-instance", "modify", instance.name], fail=True)
535
536   # Marking offline while instance is running must fail...
537   AssertCommand(["gnt-instance", "modify", "--offline", instance.name],
538                  fail=True)
539
540   # ...while making it online is ok, and should work
541   AssertCommand(["gnt-instance", "modify", "--online", instance.name])
542
543
544 @InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
545 def TestInstanceModifyPrimaryAndBack(instance, currentnode, othernode):
546   """gnt-instance modify --new-primary
547
548   This will leave the instance on its original primary node, not other node.
549
550   """
551   if instance.disk_template != constants.DT_FILE:
552     print qa_utils.FormatInfo("Test only supported for the file disk template")
553     return
554
555   cluster_name = qa_config.get("name")
556
557   name = instance.name
558   current = currentnode.primary
559   other = othernode.primary
560
561   # FIXME: the qa doesn't have a customizable file storage dir parameter. As
562   # such for now we use the default.
563   filestorage = pathutils.DEFAULT_FILE_STORAGE_DIR
564   disk = os.path.join(filestorage, name)
565
566   AssertCommand(["gnt-instance", "modify", "--new-primary=%s" % other, name],
567                 fail=True)
568   AssertCommand(["gnt-instance", "shutdown", name])
569   AssertCommand(["scp", "-oGlobalKnownHostsFile=%s" %
570                  pathutils.SSH_KNOWN_HOSTS_FILE,
571                  "-oCheckHostIp=no", "-oStrictHostKeyChecking=yes",
572                  "-oHashKnownHosts=no", "-oHostKeyAlias=%s" % cluster_name,
573                  "-r", disk, "%s:%s" % (other, filestorage)], node=current)
574   AssertCommand(["gnt-instance", "modify", "--new-primary=%s" % other, name])
575   AssertCommand(["gnt-instance", "startup", name])
576
577   # and back
578   AssertCommand(["gnt-instance", "shutdown", name])
579   AssertCommand(["rm", "-rf", disk], node=other)
580   AssertCommand(["gnt-instance", "modify", "--new-primary=%s" % current, name])
581   AssertCommand(["gnt-instance", "startup", name])
582
583
584 @InstanceCheck(INST_DOWN, INST_DOWN, FIRST_ARG)
585 def TestInstanceStoppedModify(instance):
586   """gnt-instance modify (stopped instance)"""
587   name = instance.name
588
589   # Instance was not marked offline; try marking it online once more
590   AssertCommand(["gnt-instance", "modify", "--online", name])
591
592   # Mark instance as offline
593   AssertCommand(["gnt-instance", "modify", "--offline", name])
594
595   # When the instance is offline shutdown should only work with --force,
596   # while start should never work
597   AssertCommand(["gnt-instance", "shutdown", name], fail=True)
598   AssertCommand(["gnt-instance", "shutdown", "--force", name])
599   AssertCommand(["gnt-instance", "start", name], fail=True)
600   AssertCommand(["gnt-instance", "start", "--force", name], fail=True)
601
602   # Also do offline to offline
603   AssertCommand(["gnt-instance", "modify", "--offline", name])
604
605   # And online again
606   AssertCommand(["gnt-instance", "modify", "--online", name])
607
608
609 @InstanceCheck(INST_DOWN, INST_DOWN, FIRST_ARG)
610 def TestInstanceConvertDiskToPlain(instance, inodes):
611   """gnt-instance modify -t"""
612   name = instance.name
613
614   template = instance.disk_template
615   if template != constants.DT_DRBD8:
616     print qa_utils.FormatInfo("Unsupported template %s, skipping conversion"
617                               " test" % template)
618     return
619
620   assert len(inodes) == 2
621   AssertCommand(["gnt-instance", "modify", "-t", constants.DT_PLAIN, name])
622   AssertCommand(["gnt-instance", "modify", "-t", constants.DT_DRBD8,
623                  "-n", inodes[1].primary, name])
624
625
626 @InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
627 def TestInstanceModifyDisks(instance):
628   """gnt-instance modify --disk"""
629   if not IsDiskSupported(instance):
630     print qa_utils.FormatInfo("Instance doesn't support disks, skipping test")
631     return
632
633   size = qa_config.GetDiskOptions()[-1].get("size")
634   name = instance.name
635   build_cmd = lambda arg: ["gnt-instance", "modify", "--disk", arg, name]
636   AssertCommand(build_cmd("add:size=%s" % size))
637   AssertCommand(build_cmd("remove"))
638
639
640 @InstanceCheck(INST_DOWN, INST_DOWN, FIRST_ARG)
641 def TestInstanceGrowDisk(instance):
642   """gnt-instance grow-disk"""
643   if qa_config.GetExclusiveStorage():
644     print qa_utils.FormatInfo("Test not supported with exclusive_storage")
645     return
646
647   if instance.disk_template == constants.DT_DISKLESS:
648     print qa_utils.FormatInfo("Test not supported for diskless instances")
649     return
650
651   name = instance.name
652   disks = qa_config.GetDiskOptions()
653   all_size = [d.get("size") for d in disks]
654   all_grow = [d.get("growth") for d in disks]
655
656   if not all_grow:
657     # missing disk sizes but instance grow disk has been enabled,
658     # let's set fixed/nomimal growth
659     all_grow = ["128M" for _ in all_size]
660
661   for idx, (size, grow) in enumerate(zip(all_size, all_grow)):
662     # succeed in grow by amount
663     AssertCommand(["gnt-instance", "grow-disk", name, str(idx), grow])
664     # fail in grow to the old size
665     AssertCommand(["gnt-instance", "grow-disk", "--absolute", name, str(idx),
666                    size], fail=True)
667     # succeed to grow to old size + 2 * growth
668     int_size = utils.ParseUnit(size)
669     int_grow = utils.ParseUnit(grow)
670     AssertCommand(["gnt-instance", "grow-disk", "--absolute", name, str(idx),
671                    str(int_size + 2 * int_grow)])
672
673
674 @InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
675 def TestInstanceDeviceNames(instance):
676   if instance.disk_template == constants.DT_DISKLESS:
677     print qa_utils.FormatInfo("Test not supported for diskless instances")
678     return
679
680   name = instance.name
681   for dev_type in ["disk", "net"]:
682     if dev_type == "disk":
683       options = ",size=512M"
684     else:
685       options = ""
686     # succeed in adding a device named 'test_device'
687     AssertCommand(["gnt-instance", "modify",
688                    "--%s=-1:add,name=test_device%s" % (dev_type, options),
689                    name])
690     # succeed in removing the 'test_device'
691     AssertCommand(["gnt-instance", "modify",
692                    "--%s=test_device:remove" % dev_type,
693                    name])
694     # fail to add two devices with the same name
695     AssertCommand(["gnt-instance", "modify",
696                    "--%s=-1:add,name=test_device%s" % (dev_type, options),
697                    "--%s=-1:add,name=test_device%s" % (dev_type, options),
698                    name], fail=True)
699     # fail to add a device with invalid name
700     AssertCommand(["gnt-instance", "modify",
701                    "--%s=-1:add,name=2%s" % (dev_type, options),
702                    name], fail=True)
703   # Rename disks
704   disks = qa_config.GetDiskOptions()
705   disk_names = [d.get("name") for d in disks]
706   for idx, disk_name in enumerate(disk_names):
707     # Refer to disk by idx
708     AssertCommand(["gnt-instance", "modify",
709                    "--disk=%s:modify,name=renamed" % idx,
710                    name])
711     # Refer to by name and rename to original name
712     AssertCommand(["gnt-instance", "modify",
713                    "--disk=renamed:modify,name=%s" % disk_name,
714                    name])
715   if len(disks) >= 2:
716     # fail in renaming to disks to the same name
717     AssertCommand(["gnt-instance", "modify",
718                    "--disk=0:modify,name=same_name",
719                    "--disk=1:modify,name=same_name",
720                    name], fail=True)
721
722
723 def TestInstanceList():
724   """gnt-instance list"""
725   qa_utils.GenericQueryTest("gnt-instance", query.INSTANCE_FIELDS.keys())
726
727
728 def TestInstanceListFields():
729   """gnt-instance list-fields"""
730   qa_utils.GenericQueryFieldsTest("gnt-instance", query.INSTANCE_FIELDS.keys())
731
732
733 @InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
734 def TestInstanceConsole(instance):
735   """gnt-instance console"""
736   AssertCommand(["gnt-instance", "console", "--show-cmd", instance.name])
737
738
739 @InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
740 def TestReplaceDisks(instance, curr_nodes, other_nodes):
741   """gnt-instance replace-disks"""
742   def buildcmd(args):
743     cmd = ["gnt-instance", "replace-disks"]
744     cmd.extend(args)
745     cmd.append(instance.name)
746     return cmd
747
748   if not IsDiskReplacingSupported(instance):
749     print qa_utils.FormatInfo("Instance doesn't support disk replacing,"
750                               " skipping test")
751     return
752
753   # Currently all supported templates have one primary and one secondary node
754   assert len(curr_nodes) == 2
755   snode = curr_nodes[1]
756   assert len(other_nodes) == 1
757   othernode = other_nodes[0]
758
759   options = qa_config.get("options", {})
760   use_ialloc = options.get("use-iallocators", True)
761   for data in [
762     ["-p"],
763     ["-s"],
764     # A placeholder; the actual command choice depends on use_ialloc
765     None,
766     # Restore the original secondary
767     ["--new-secondary=%s" % snode.primary],
768     ]:
769     if data is None:
770       if use_ialloc:
771         data = ["-I", constants.DEFAULT_IALLOCATOR_SHORTCUT]
772       else:
773         data = ["--new-secondary=%s" % othernode.primary]
774     AssertCommand(buildcmd(data))
775
776   AssertCommand(buildcmd(["-a"]))
777   AssertCommand(["gnt-instance", "stop", instance.name])
778   AssertCommand(buildcmd(["-a"]), fail=True)
779   AssertCommand(["gnt-instance", "activate-disks", instance.name])
780   AssertCommand(["gnt-instance", "activate-disks", "--wait-for-sync",
781                  instance.name])
782   AssertCommand(buildcmd(["-a"]))
783   AssertCommand(["gnt-instance", "start", instance.name])
784
785
786 def _AssertRecreateDisks(cmdargs, instance, fail=False, check=True,
787                          destroy=True):
788   """Execute gnt-instance recreate-disks and check the result
789
790   @param cmdargs: Arguments (instance name excluded)
791   @param instance: Instance to operate on
792   @param fail: True if the command is expected to fail
793   @param check: If True and fail is False, check that the disks work
794   @prama destroy: If True, destroy the old disks first
795
796   """
797   if destroy:
798     _DestroyInstanceDisks(instance)
799   AssertCommand((["gnt-instance", "recreate-disks"] + cmdargs +
800                  [instance.name]), fail)
801   if not fail and check:
802     # Quick check that the disks are there
803     AssertCommand(["gnt-instance", "activate-disks", instance.name])
804     AssertCommand(["gnt-instance", "activate-disks", "--wait-for-sync",
805                    instance.name])
806     AssertCommand(["gnt-instance", "deactivate-disks", instance.name])
807
808
809 @InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
810 def TestRecreateDisks(instance, inodes, othernodes):
811   """gnt-instance recreate-disks
812
813   @param instance: Instance to work on
814   @param inodes: List of the current nodes of the instance
815   @param othernodes: list/tuple of nodes where to temporarily recreate disks
816
817   """
818   options = qa_config.get("options", {})
819   use_ialloc = options.get("use-iallocators", True)
820   other_seq = ":".join([n.primary for n in othernodes])
821   orig_seq = ":".join([n.primary for n in inodes])
822   # These fail because the instance is running
823   _AssertRecreateDisks(["-n", other_seq], instance, fail=True, destroy=False)
824   if use_ialloc:
825     _AssertRecreateDisks(["-I", "hail"], instance, fail=True, destroy=False)
826   else:
827     _AssertRecreateDisks(["-n", other_seq], instance, fail=True, destroy=False)
828   AssertCommand(["gnt-instance", "stop", instance.name])
829   # Disks exist: this should fail
830   _AssertRecreateDisks([], instance, fail=True, destroy=False)
831   # Unsupported spindles parameters: fail
832   if not qa_config.AreSpindlesSupported():
833     _AssertRecreateDisks(["--disk=0:spindles=2"], instance,
834                          fail=True, destroy=False)
835   # Recreate disks in place
836   _AssertRecreateDisks([], instance)
837   # Move disks away
838   if use_ialloc:
839     _AssertRecreateDisks(["-I", "hail"], instance)
840     # Move disks somewhere else
841     _AssertRecreateDisks(["-I", constants.DEFAULT_IALLOCATOR_SHORTCUT],
842                          instance)
843   else:
844     _AssertRecreateDisks(["-n", other_seq], instance)
845   # Move disks back
846   _AssertRecreateDisks(["-n", orig_seq], instance)
847   # Recreate resized disks
848   alldisks = qa_config.GetDiskOptions()
849   if qa_config.AreSpindlesSupported():
850     build_disks_opt = (lambda idx, disk:
851                        ("--disk=%s:size=%s,spindles=%s" %
852                         (idx, (utils.ParseUnit(disk["size"]) +
853                                utils.ParseUnit(disk["growth"])),
854                          disk["spindles"] + disk["spindles-growth"])))
855   else:
856     build_disks_opt = (lambda idx, disk:
857                        ("--disk=%s:size=%s" %
858                         (idx, (utils.ParseUnit(disk["size"]) +
859                                utils.ParseUnit(disk["growth"])))))
860   disk_opts = map(build_disks_opt, range(0, len(alldisks)), (alldisks))
861   _AssertRecreateDisks(disk_opts, instance)
862   # Recreate the disks one by one (with the original size)
863   if qa_config.AreSpindlesSupported():
864     build_disks_opt = lambda idx, disk: ("--disk=%s:size=%s,spindles=%s" %
865                                          (idx, disk["size"], disk["spindles"]))
866   else:
867     build_disks_opt = lambda idx, disk: ("--disk=%s:size=%s" %
868                                          (idx, disk["size"]))
869   for (idx, disk) in enumerate(alldisks):
870     # Only the first call should destroy all the disk
871     destroy = (idx == 0)
872     _AssertRecreateDisks([build_disks_opt(idx, disk)], instance,
873                          destroy=destroy, check=False)
874   # This and InstanceCheck decoration check that the disks are working
875   AssertCommand(["gnt-instance", "reinstall", "-f", instance.name])
876   AssertCommand(["gnt-instance", "start", instance.name])
877
878
879 @InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
880 def TestInstanceExport(instance, node):
881   """gnt-backup export -n ..."""
882   name = instance.name
883   AssertCommand(["gnt-backup", "export", "-n", node.primary, name])
884   return qa_utils.ResolveInstanceName(name)
885
886
887 @InstanceCheck(None, INST_DOWN, FIRST_ARG)
888 def TestInstanceExportWithRemove(instance, node):
889   """gnt-backup export --remove-instance"""
890   AssertCommand(["gnt-backup", "export", "-n", node.primary,
891                  "--remove-instance", instance.name])
892
893
894 @InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
895 def TestInstanceExportNoTarget(instance):
896   """gnt-backup export (without target node, should fail)"""
897   AssertCommand(["gnt-backup", "export", instance.name], fail=True)
898
899
900 @InstanceCheck(None, INST_DOWN, FIRST_ARG)
901 def TestInstanceImport(newinst, node, expnode, name):
902   """gnt-backup import"""
903   templ = constants.DT_PLAIN
904   cmd = (["gnt-backup", "import",
905           "--disk-template=%s" % templ,
906           "--no-ip-check",
907           "--src-node=%s" % expnode.primary,
908           "--src-dir=%s/%s" % (pathutils.EXPORT_DIR, name),
909           "--node=%s" % node.primary] +
910          GetGenericAddParameters(newinst, templ,
911                                   force_mac=constants.VALUE_GENERATE))
912   cmd.append(newinst.name)
913   AssertCommand(cmd)
914   newinst.SetDiskTemplate(templ)
915
916
917 def TestBackupList(expnode):
918   """gnt-backup list"""
919   AssertCommand(["gnt-backup", "list", "--node=%s" % expnode.primary])
920
921   qa_utils.GenericQueryTest("gnt-backup", query.EXPORT_FIELDS.keys(),
922                             namefield=None, test_unknown=False)
923
924
925 def TestBackupListFields():
926   """gnt-backup list-fields"""
927   qa_utils.GenericQueryFieldsTest("gnt-backup", query.EXPORT_FIELDS.keys())
928
929
930 def TestRemoveInstanceOfflineNode(instance, snode, set_offline, set_online):
931   """gnt-instance remove with an off-line node
932
933   @param instance: instance
934   @param snode: secondary node, to be set offline
935   @param set_offline: function to call to set the node off-line
936   @param set_online: function to call to set the node on-line
937
938   """
939   info = _GetInstanceInfo(instance.name)
940   set_offline(snode)
941   try:
942     TestInstanceRemove(instance)
943   finally:
944     set_online(snode)
945
946   # Clean up the disks on the offline node, if necessary
947   if instance.disk_template not in constants.DTS_EXT_MIRROR:
948     # FIXME: abstract the cleanup inside the disks
949     if info["storage-type"] == constants.ST_LVM_VG:
950       for minor in info["drbd-minors"][snode.primary]:
951         # DRBD 8.3 syntax comes first, then DRBD 8.4 syntax. The 8.4 syntax
952         # relies on the fact that we always create a resources for each minor,
953         # and that this resources is always named resource{minor}.
954         # As 'drbdsetup 0 down' does return success (even though that's invalid
955         # syntax), we always have to perform both commands and ignore the
956         # output.
957         drbd_shutdown_cmd = \
958           "(drbdsetup %d down && drbdsetup down resource%d) || /bin/true" % \
959             (minor, minor)
960         AssertCommand(drbd_shutdown_cmd, node=snode)
961       AssertCommand(["lvremove", "-f"] + info["volumes"], node=snode)
962     elif info["storage-type"] == constants.ST_FILE:
963       filestorage = pathutils.DEFAULT_FILE_STORAGE_DIR
964       disk = os.path.join(filestorage, instance.name)
965       AssertCommand(["rm", "-rf", disk], node=snode)
966
967
968 def TestInstanceCreationRestrictedByDiskTemplates():
969   """Test adding instances for disbled disk templates."""
970   enabled_disk_templates = qa_config.GetEnabledDiskTemplates()
971   nodes = qa_config.AcquireManyNodes(2)
972
973   # Setup the cluster with the enabled_disk_templates
974   AssertCommand(
975     ["gnt-cluster", "modify",
976      "--enabled-disk-template=%s" %
977        ",".join(enabled_disk_templates)],
978     fail=False)
979
980   # Test instance creation for enabled disk templates
981   for disk_template in enabled_disk_templates:
982     instance = CreateInstanceByDiskTemplate(nodes, disk_template, False)
983     TestInstanceRemove(instance)
984
985   # Test that instance creation fails for disabled disk templates
986   disabled_disk_templates = list(constants.DISK_TEMPLATES
987                                  - set(enabled_disk_templates))
988   for disk_template in disabled_disk_templates:
989     instance = CreateInstanceByDiskTemplate(nodes, disk_template, True)
990
991   # Test instance creation for after disabling enabled disk templates
992   if (len(enabled_disk_templates) > 1):
993     # Partition the disk templates, enable them separately and check if the
994     # disabled ones cannot be used by instances.
995     middle = len(enabled_disk_templates) / 2
996     templates1 = enabled_disk_templates[:middle]
997     templates2 = enabled_disk_templates[middle:]
998
999     for (enabled, disabled) in [(templates1, templates2),
1000                                 (templates2, templates1)]:
1001       AssertCommand(["gnt-cluster", "modify",
1002                      "--enabled-disk-template=%s" %
1003                        ",".join(enabled)],
1004                     fail=False)
1005       for disk_template in disabled:
1006         CreateInstanceByDiskTemplate(nodes, disk_template, True)
1007   elif (len(enabled_disk_templates) == 1):
1008     # If only one disk template is enabled in the QA config, we have to enable
1009     # some of the disabled disk templates in order to test if the disabling the
1010     # only enabled disk template prohibits creating instances of that template.
1011     AssertCommand(["gnt-cluster", "modify",
1012                    "--enabled-disk-template=%s" %
1013                      ",".join(disabled_disk_templates)],
1014                   fail=False)
1015     CreateInstanceByDiskTemplate(nodes, enabled_disk_templates[0], True)
1016   else:
1017     raise qa_error.Error("Please enable at least one disk template"
1018                          " in your QA setup.")
1019
1020   # Restore initially enabled disk templates
1021   AssertCommand(["gnt-cluster", "modify",
1022                  "--enabled-disk-template=%s" %
1023                    ",".join(enabled_disk_templates)],
1024                  fail=False)