3ea72631ee3a2c430b25d6ffe96b502947010298
[ganeti-local] / qa / qa_cluster.py
1 #
2 #
3
4 # Copyright (C) 2007, 2010, 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 """Cluster related QA tests.
23
24 """
25
26 import re
27 import tempfile
28 import os.path
29
30 from ganeti import constants
31 from ganeti import compat
32 from ganeti import utils
33 from ganeti import pathutils
34
35 import qa_config
36 import qa_utils
37 import qa_error
38 import qa_instance
39
40 from qa_utils import AssertEqual, AssertCommand, GetCommandOutput
41
42
43 # Prefix for LVM volumes created by QA code during tests
44 _QA_LV_PREFIX = "qa-"
45
46 #: cluster verify command
47 _CLUSTER_VERIFY = ["gnt-cluster", "verify"]
48
49
50 def _RemoveFileFromAllNodes(filename):
51   """Removes a file from all nodes.
52
53   """
54   for node in qa_config.get("nodes"):
55     AssertCommand(["rm", "-f", filename], node=node)
56
57
58 def _CheckFileOnAllNodes(filename, content):
59   """Verifies the content of the given file on all nodes.
60
61   """
62   cmd = utils.ShellQuoteArgs(["cat", filename])
63   for node in qa_config.get("nodes"):
64     AssertEqual(qa_utils.GetCommandOutput(node.primary, cmd), content)
65
66
67 def _GetClusterField(field_path):
68   """Get the value of a cluster field.
69
70   @type field_path: list of strings
71   @param field_path: Names of the groups/fields to navigate to get the desired
72       value, e.g. C{["Default node parameters", "oob_program"]}
73   @return: The effective value of the field (the actual type depends on the
74       chosen field)
75
76   """
77   assert isinstance(field_path, list)
78   assert field_path
79   ret = qa_utils.GetObjectInfo(["gnt-cluster", "info"])
80   for key in field_path:
81     ret = ret[key]
82   return ret
83
84
85 # Cluster-verify errors (date, "ERROR", then error code)
86 _CVERROR_RE = re.compile(r"^[\w\s:]+\s+- (ERROR|WARNING):([A-Z0-9_-]+):")
87
88
89 def _GetCVErrorCodes(cvout):
90   errs = set()
91   warns = set()
92   for l in cvout.splitlines():
93     m = _CVERROR_RE.match(l)
94     if m:
95       etype = m.group(1)
96       ecode = m.group(2)
97       if etype == "ERROR":
98         errs.add(ecode)
99       elif etype == "WARNING":
100         warns.add(ecode)
101   return (errs, warns)
102
103
104 def _CheckVerifyErrors(actual, expected, etype):
105   exp_codes = compat.UniqueFrozenset(e for (_, e, _) in expected)
106   if not actual.issuperset(exp_codes):
107     missing = exp_codes.difference(actual)
108     raise qa_error.Error("Cluster-verify didn't return these expected"
109                          " %ss: %s" % (etype, utils.CommaJoin(missing)))
110
111
112 def AssertClusterVerify(fail=False, errors=None, warnings=None):
113   """Run cluster-verify and check the result
114
115   @type fail: bool
116   @param fail: if cluster-verify is expected to fail instead of succeeding
117   @type errors: list of tuples
118   @param errors: List of CV_XXX errors that are expected; if specified, all the
119       errors listed must appear in cluster-verify output. A non-empty value
120       implies C{fail=True}.
121   @type warnings: list of tuples
122   @param warnings: Same as C{errors} but for warnings.
123
124   """
125   cvcmd = "gnt-cluster verify"
126   mnode = qa_config.GetMasterNode()
127   if errors or warnings:
128     cvout = GetCommandOutput(mnode.primary, cvcmd + " --error-codes",
129                              fail=(fail or errors))
130     (act_errs, act_warns) = _GetCVErrorCodes(cvout)
131     if errors:
132       _CheckVerifyErrors(act_errs, errors, "error")
133     if warnings:
134       _CheckVerifyErrors(act_warns, warnings, "warning")
135   else:
136     AssertCommand(cvcmd, fail=fail, node=mnode)
137
138
139 # data for testing failures due to bad keys/values for disk parameters
140 _FAIL_PARAMS = ["nonexistent:resync-rate=1",
141                 "drbd:nonexistent=1",
142                 "drbd:resync-rate=invalid",
143                 ]
144
145
146 def TestClusterInitDisk():
147   """gnt-cluster init -D"""
148   name = qa_config.get("name")
149   for param in _FAIL_PARAMS:
150     AssertCommand(["gnt-cluster", "init", "-D", param, name], fail=True)
151
152
153 def TestClusterInit(rapi_user, rapi_secret):
154   """gnt-cluster init"""
155   master = qa_config.GetMasterNode()
156
157   rapi_users_path = qa_utils.MakeNodePath(master, pathutils.RAPI_USERS_FILE)
158   rapi_dir = os.path.dirname(rapi_users_path)
159
160   # First create the RAPI credentials
161   fh = tempfile.NamedTemporaryFile()
162   try:
163     fh.write("%s %s write\n" % (rapi_user, rapi_secret))
164     fh.flush()
165
166     tmpru = qa_utils.UploadFile(master.primary, fh.name)
167     try:
168       AssertCommand(["mkdir", "-p", rapi_dir])
169       AssertCommand(["mv", tmpru, rapi_users_path])
170     finally:
171       AssertCommand(["rm", "-f", tmpru])
172   finally:
173     fh.close()
174
175   # Initialize cluster
176   cmd = [
177     "gnt-cluster", "init",
178     "--primary-ip-version=%d" % qa_config.get("primary_ip_version", 4),
179     "--enabled-hypervisors=%s" % ",".join(qa_config.GetEnabledHypervisors()),
180     "--enabled-disk-templates=%s" %
181       ",".join(qa_config.GetEnabledDiskTemplates())
182     ]
183
184   for spec_type in ("mem-size", "disk-size", "disk-count", "cpu-count",
185                     "nic-count"):
186     for spec_val in ("min", "max", "std"):
187       spec = qa_config.get("ispec_%s_%s" %
188                            (spec_type.replace("-", "_"), spec_val), None)
189       if spec is not None:
190         cmd.append("--specs-%s=%s=%d" % (spec_type, spec_val, spec))
191
192   if master.secondary:
193     cmd.append("--secondary-ip=%s" % master.secondary)
194
195   if utils.IsLvmEnabled(qa_config.GetEnabledDiskTemplates()):
196     vgname = qa_config.get("vg-name", constants.DEFAULT_VG)
197     if vgname:
198       cmd.append("--vg-name=%s" % vgname)
199     else:
200       raise qa_error.Error("Please specify a volume group if you enable"
201                            " lvm-based disk templates in the QA.")
202
203   master_netdev = qa_config.get("master-netdev", None)
204   if master_netdev:
205     cmd.append("--master-netdev=%s" % master_netdev)
206
207   nicparams = qa_config.get("default-nicparams", None)
208   if nicparams:
209     cmd.append("--nic-parameters=%s" %
210                ",".join(utils.FormatKeyValue(nicparams)))
211
212   # Cluster value of the exclusive-storage node parameter
213   e_s = qa_config.get("exclusive-storage")
214   if e_s is not None:
215     cmd.extend(["--node-parameters", "exclusive_storage=%s" % e_s])
216   else:
217     e_s = False
218   qa_config.SetExclusiveStorage(e_s)
219
220   extra_args = qa_config.get("cluster-init-args")
221   if extra_args:
222     cmd.extend(extra_args)
223
224   cmd.append(qa_config.get("name"))
225
226   AssertCommand(cmd)
227
228   cmd = ["gnt-cluster", "modify"]
229
230   # hypervisor parameter modifications
231   hvp = qa_config.get("hypervisor-parameters", {})
232   for k, v in hvp.items():
233     cmd.extend(["-H", "%s:%s" % (k, v)])
234   # backend parameter modifications
235   bep = qa_config.get("backend-parameters", "")
236   if bep:
237     cmd.extend(["-B", bep])
238
239   if len(cmd) > 2:
240     AssertCommand(cmd)
241
242   # OS parameters
243   osp = qa_config.get("os-parameters", {})
244   for k, v in osp.items():
245     AssertCommand(["gnt-os", "modify", "-O", v, k])
246
247   # OS hypervisor parameters
248   os_hvp = qa_config.get("os-hvp", {})
249   for os_name in os_hvp:
250     for hv, hvp in os_hvp[os_name].items():
251       AssertCommand(["gnt-os", "modify", "-H", "%s:%s" % (hv, hvp), os_name])
252
253
254 def TestClusterRename():
255   """gnt-cluster rename"""
256   cmd = ["gnt-cluster", "rename", "-f"]
257
258   original_name = qa_config.get("name")
259   rename_target = qa_config.get("rename", None)
260   if rename_target is None:
261     print qa_utils.FormatError('"rename" entry is missing')
262     return
263
264   for data in [
265     cmd + [rename_target],
266     _CLUSTER_VERIFY,
267     cmd + [original_name],
268     _CLUSTER_VERIFY,
269     ]:
270     AssertCommand(data)
271
272
273 def TestClusterOob():
274   """out-of-band framework"""
275   oob_path_exists = "/tmp/ganeti-qa-oob-does-exist-%s" % utils.NewUUID()
276
277   AssertCommand(_CLUSTER_VERIFY)
278   AssertCommand(["gnt-cluster", "modify", "--node-parameters",
279                  "oob_program=/tmp/ganeti-qa-oob-does-not-exist-%s" %
280                  utils.NewUUID()])
281
282   AssertCommand(_CLUSTER_VERIFY, fail=True)
283
284   AssertCommand(["touch", oob_path_exists])
285   AssertCommand(["chmod", "0400", oob_path_exists])
286   AssertCommand(["gnt-cluster", "copyfile", oob_path_exists])
287
288   try:
289     AssertCommand(["gnt-cluster", "modify", "--node-parameters",
290                    "oob_program=%s" % oob_path_exists])
291
292     AssertCommand(_CLUSTER_VERIFY, fail=True)
293
294     AssertCommand(["chmod", "0500", oob_path_exists])
295     AssertCommand(["gnt-cluster", "copyfile", oob_path_exists])
296
297     AssertCommand(_CLUSTER_VERIFY)
298   finally:
299     AssertCommand(["gnt-cluster", "command", "rm", oob_path_exists])
300
301   AssertCommand(["gnt-cluster", "modify", "--node-parameters",
302                  "oob_program="])
303
304
305 def TestClusterEpo():
306   """gnt-cluster epo"""
307   master = qa_config.GetMasterNode()
308
309   # Assert that OOB is unavailable for all nodes
310   result_output = GetCommandOutput(master.primary,
311                                    "gnt-node list --verbose --no-headers -o"
312                                    " powered")
313   AssertEqual(compat.all(powered == "(unavail)"
314                          for powered in result_output.splitlines()), True)
315
316   # Conflicting
317   AssertCommand(["gnt-cluster", "epo", "--groups", "--all"], fail=True)
318   # --all doesn't expect arguments
319   AssertCommand(["gnt-cluster", "epo", "--all", "some_arg"], fail=True)
320
321   # Unless --all is given master is not allowed to be in the list
322   AssertCommand(["gnt-cluster", "epo", "-f", master.primary], fail=True)
323
324   # This shouldn't fail
325   AssertCommand(["gnt-cluster", "epo", "-f", "--all"])
326
327   # All instances should have been stopped now
328   result_output = GetCommandOutput(master.primary,
329                                    "gnt-instance list --no-headers -o status")
330   # ERROR_down because the instance is stopped but not recorded as such
331   AssertEqual(compat.all(status == "ERROR_down"
332                          for status in result_output.splitlines()), True)
333
334   # Now start everything again
335   AssertCommand(["gnt-cluster", "epo", "--on", "-f", "--all"])
336
337   # All instances should have been started now
338   result_output = GetCommandOutput(master.primary,
339                                    "gnt-instance list --no-headers -o status")
340   AssertEqual(compat.all(status == "running"
341                          for status in result_output.splitlines()), True)
342
343
344 def TestClusterVerify():
345   """gnt-cluster verify"""
346   AssertCommand(_CLUSTER_VERIFY)
347   AssertCommand(["gnt-cluster", "verify-disks"])
348
349
350 def TestJobqueue():
351   """gnt-debug test-jobqueue"""
352   AssertCommand(["gnt-debug", "test-jobqueue"])
353
354
355 def TestDelay(node):
356   """gnt-debug delay"""
357   AssertCommand(["gnt-debug", "delay", "1"])
358   AssertCommand(["gnt-debug", "delay", "--no-master", "1"])
359   AssertCommand(["gnt-debug", "delay", "--no-master",
360                  "-n", node.primary, "1"])
361
362
363 def TestClusterReservedLvs():
364   """gnt-cluster reserved lvs"""
365   vgname = qa_config.get("vg-name", constants.DEFAULT_VG)
366   lvname = _QA_LV_PREFIX + "test"
367   lvfullname = "/".join([vgname, lvname])
368   for fail, cmd in [
369     (False, _CLUSTER_VERIFY),
370     (False, ["gnt-cluster", "modify", "--reserved-lvs", ""]),
371     (False, ["lvcreate", "-L1G", "-n", lvname, vgname]),
372     (True, _CLUSTER_VERIFY),
373     (False, ["gnt-cluster", "modify", "--reserved-lvs",
374              "%s,.*/other-test" % lvfullname]),
375     (False, _CLUSTER_VERIFY),
376     (False, ["gnt-cluster", "modify", "--reserved-lvs",
377              ".*/%s.*" % _QA_LV_PREFIX]),
378     (False, _CLUSTER_VERIFY),
379     (False, ["gnt-cluster", "modify", "--reserved-lvs", ""]),
380     (True, _CLUSTER_VERIFY),
381     (False, ["lvremove", "-f", lvfullname]),
382     (False, _CLUSTER_VERIFY),
383     ]:
384     AssertCommand(cmd, fail=fail)
385
386
387 def TestClusterModifyEmpty():
388   """gnt-cluster modify"""
389   AssertCommand(["gnt-cluster", "modify"], fail=True)
390
391
392 def TestClusterModifyDisk():
393   """gnt-cluster modify -D"""
394   for param in _FAIL_PARAMS:
395     AssertCommand(["gnt-cluster", "modify", "-D", param], fail=True)
396
397
398 def TestClusterModifyDiskTemplates():
399   """gnt-cluster modify --enabled-disk-templates=..."""
400   enabled_disk_templates = qa_config.GetEnabledDiskTemplates()
401   default_disk_template = qa_config.GetDefaultDiskTemplate()
402
403   _TestClusterModifyDiskTemplatesArguments(default_disk_template,
404                                            enabled_disk_templates)
405   _TestClusterModifyDiskTemplatesVgName(enabled_disk_templates)
406
407   _RestoreEnabledDiskTemplates()
408   nodes = qa_config.AcquireManyNodes(2)
409
410   instance_template = enabled_disk_templates[0]
411   instance = qa_instance.CreateInstanceByDiskTemplate(nodes, instance_template)
412
413   _TestClusterModifyUnusedDiskTemplate(instance_template)
414   _TestClusterModifyUsedDiskTemplate(instance_template,
415                                      enabled_disk_templates)
416
417   qa_instance.TestInstanceRemove(instance)
418   _RestoreEnabledDiskTemplates()
419
420
421 def _RestoreEnabledDiskTemplates():
422   """Sets the list of enabled disk templates back to the list of enabled disk
423      templates from the QA configuration. This can be used to make sure that
424      the tests that modify the list of disk templates do not interfere with
425      other tests.
426
427   """
428   cmd = ["gnt-cluster", "modify", "--enabled-disk-templates=%s" %
429          ",".join(qa_config.GetEnabledDiskTemplates())]
430
431   if utils.IsLvmEnabled(qa_config.GetEnabledDiskTemplates()):
432     vgname = qa_config.get("vg-name", constants.DEFAULT_VG)
433     cmd.append("--vg-name=%s" % vgname)
434
435   AssertCommand(cmd, fail=False)
436
437
438 def _TestClusterModifyDiskTemplatesArguments(default_disk_template,
439                                              enabled_disk_templates):
440   """Tests argument handling of 'gnt-cluster modify' with respect to
441      the parameter '--enabled-disk-templates'. This test is independent
442      of instances.
443
444   """
445   _RestoreEnabledDiskTemplates()
446
447   # bogus templates
448   AssertCommand(["gnt-cluster", "modify",
449                  "--enabled-disk-templates=pinkbunny"],
450                 fail=True)
451
452   # duplicate entries do no harm
453   AssertCommand(
454     ["gnt-cluster", "modify",
455      "--enabled-disk-templates=%s,%s" %
456       (default_disk_template, default_disk_template)],
457     fail=False)
458
459   if constants.DT_DRBD8 in enabled_disk_templates:
460     # interaction with --drbd-usermode-helper option
461     drbd_usermode_helper = qa_config.get("drbd-usermode-helper", None)
462     if not drbd_usermode_helper:
463       drbd_usermode_helper = "/bin/true"
464     # specifying a helper when drbd gets disabled is ok. Note that drbd still
465     # has to be installed on the nodes in this case
466     AssertCommand(["gnt-cluster", "modify",
467                    "--drbd-usermode-helper=%s" % drbd_usermode_helper,
468                    "--enabled-disk-templates=%s" % constants.DT_DISKLESS],
469                    fail=False)
470     # specifying a helper when drbd is re-enabled
471     AssertCommand(["gnt-cluster", "modify",
472                    "--drbd-usermode-helper=%s" % drbd_usermode_helper,
473                    "--enabled-disk-templates=%s" %
474                      ",".join(enabled_disk_templates)],
475                   fail=False)
476
477
478 def _TestClusterModifyDiskTemplatesVgName(enabled_disk_templates):
479   """Tests argument handling of 'gnt-cluster modify' with respect to
480      the parameter '--enabled-disk-templates' and '--vg-name'. This test is
481      independent of instances.
482
483   """
484   if not utils.IsLvmEnabled(enabled_disk_templates):
485     # These tests only make sense if lvm is enabled for QA
486     return
487
488   # determine an LVM and a non-LVM disk template for the tests
489   non_lvm_templates = list(set(enabled_disk_templates)
490                            - set(utils.GetLvmDiskTemplates()))
491   lvm_template = list(set(enabled_disk_templates)
492                       .intersection(set(utils.GetLvmDiskTemplates())))[0]
493   non_lvm_template = None
494   if non_lvm_templates:
495     non_lvm_template = non_lvm_templates[0]
496   else:
497     # If no non-lvm disk template is available for QA, choose 'diskless' and
498     # hope for the best.
499     non_lvm_template = constants.ST_DISKLESS
500
501   vgname = qa_config.get("vg-name", constants.DEFAULT_VG)
502
503   # Clean start: unset volume group name, disable lvm storage
504   AssertCommand(
505     ["gnt-cluster", "modify",
506      "--enabled-disk-templates=%s" % non_lvm_template,
507      "--vg-name="],
508     fail=False)
509
510   # Try to enable lvm, when no volume group is given
511   AssertCommand(
512     ["gnt-cluster", "modify",
513      "--enabled-disk-templates=%s" % lvm_template],
514     fail=True)
515
516   # Set volume group, with lvm still disabled: just a warning
517   AssertCommand(["gnt-cluster", "modify", "--vg-name=%s" % vgname], fail=False)
518
519   # Try unsetting vg name and enabling lvm at the same time
520   AssertCommand(
521     ["gnt-cluster", "modify",
522      "--enabled-disk-templates=%s" % lvm_template,
523      "--vg-name="],
524     fail=True)
525
526   # Enable lvm with vg name present
527   AssertCommand(
528     ["gnt-cluster", "modify",
529      "--enabled-disk-templates=%s" % lvm_template],
530     fail=False)
531
532   # Try unsetting vg name with lvm still enabled
533   AssertCommand(["gnt-cluster", "modify", "--vg-name="], fail=True)
534
535   # Disable lvm with vg name still set
536   AssertCommand(
537     ["gnt-cluster", "modify", "--enabled-disk-templates=%s" % non_lvm_template],
538     fail=False)
539
540   # Try unsetting vg name with lvm disabled
541   AssertCommand(["gnt-cluster", "modify", "--vg-name="], fail=False)
542
543   # Set vg name and enable lvm at the same time
544   AssertCommand(
545     ["gnt-cluster", "modify",
546      "--enabled-disk-templates=%s" % lvm_template,
547      "--vg-name=%s" % vgname],
548     fail=False)
549
550   # Unset vg name and disable lvm at the same time
551   AssertCommand(
552     ["gnt-cluster", "modify",
553      "--enabled-disk-templates=%s" % non_lvm_template,
554      "--vg-name="],
555     fail=False)
556
557   _RestoreEnabledDiskTemplates()
558
559
560 def _TestClusterModifyUsedDiskTemplate(instance_template,
561                                        enabled_disk_templates):
562   """Tests that disk templates that are currently in use by instances cannot
563      be disabled on the cluster.
564
565   """
566   # If the list of enabled disk templates contains only one template
567   # we need to add some other templates, because the list of enabled disk
568   # templates can only be set to a non-empty list.
569   new_disk_templates = list(set(enabled_disk_templates)
570                               - set([instance_template]))
571   if not new_disk_templates:
572     new_disk_templates = list(set([constants.DT_DISKLESS, constants.DT_BLOCK])
573                                 - set([instance_template]))
574   AssertCommand(
575     ["gnt-cluster", "modify",
576      "--enabled-disk-templates=%s" %
577        ",".join(new_disk_templates)],
578     fail=True)
579
580
581 def _TestClusterModifyUnusedDiskTemplate(instance_template):
582   """Tests that unused disk templates can be disabled safely."""
583   all_disk_templates = constants.DISK_TEMPLATES
584   if not utils.IsLvmEnabled(qa_config.GetEnabledDiskTemplates()):
585     all_disk_templates = list(set(all_disk_templates) -
586                               set(utils.GetLvmDiskTemplates()))
587
588   AssertCommand(
589     ["gnt-cluster", "modify",
590      "--enabled-disk-templates=%s" %
591        ",".join(all_disk_templates)],
592     fail=False)
593   new_disk_templates = [instance_template]
594   AssertCommand(
595     ["gnt-cluster", "modify",
596      "--enabled-disk-templates=%s" %
597        ",".join(new_disk_templates)],
598     fail=False)
599
600
601 def TestClusterModifyBe():
602   """gnt-cluster modify -B"""
603   for fail, cmd in [
604     # max/min mem
605     (False, ["gnt-cluster", "modify", "-B", "maxmem=256"]),
606     (False, ["sh", "-c", "gnt-cluster info|grep '^ *maxmem: 256$'"]),
607     (False, ["gnt-cluster", "modify", "-B", "minmem=256"]),
608     (False, ["sh", "-c", "gnt-cluster info|grep '^ *minmem: 256$'"]),
609     (True, ["gnt-cluster", "modify", "-B", "maxmem=a"]),
610     (False, ["sh", "-c", "gnt-cluster info|grep '^ *maxmem: 256$'"]),
611     (True, ["gnt-cluster", "modify", "-B", "minmem=a"]),
612     (False, ["sh", "-c", "gnt-cluster info|grep '^ *minmem: 256$'"]),
613     (False, ["gnt-cluster", "modify", "-B", "maxmem=128,minmem=128"]),
614     (False, ["sh", "-c", "gnt-cluster info|grep '^ *maxmem: 128$'"]),
615     (False, ["sh", "-c", "gnt-cluster info|grep '^ *minmem: 128$'"]),
616     # vcpus
617     (False, ["gnt-cluster", "modify", "-B", "vcpus=4"]),
618     (False, ["sh", "-c", "gnt-cluster info|grep '^ *vcpus: 4$'"]),
619     (True, ["gnt-cluster", "modify", "-B", "vcpus=a"]),
620     (False, ["gnt-cluster", "modify", "-B", "vcpus=1"]),
621     (False, ["sh", "-c", "gnt-cluster info|grep '^ *vcpus: 1$'"]),
622     # auto_balance
623     (False, ["gnt-cluster", "modify", "-B", "auto_balance=False"]),
624     (False, ["sh", "-c", "gnt-cluster info|grep '^ *auto_balance: False$'"]),
625     (True, ["gnt-cluster", "modify", "-B", "auto_balance=1"]),
626     (False, ["gnt-cluster", "modify", "-B", "auto_balance=True"]),
627     (False, ["sh", "-c", "gnt-cluster info|grep '^ *auto_balance: True$'"]),
628     ]:
629     AssertCommand(cmd, fail=fail)
630
631   # redo the original-requested BE parameters, if any
632   bep = qa_config.get("backend-parameters", "")
633   if bep:
634     AssertCommand(["gnt-cluster", "modify", "-B", bep])
635
636
637 def _GetClusterIPolicy():
638   """Return the run-time values of the cluster-level instance policy.
639
640   @rtype: tuple
641   @return: (policy, specs), where:
642       - policy is a dictionary of the policy values, instance specs excluded
643       - specs is a dictionary containing only the specs, using the internal
644         format (see L{constants.IPOLICY_DEFAULTS} for an example)
645
646   """
647   info = qa_utils.GetObjectInfo(["gnt-cluster", "info"])
648   policy = info["Instance policy - limits for instances"]
649   (ret_policy, ret_specs) = qa_utils.ParseIPolicy(policy)
650
651   # Sanity checks
652   assert "minmax" in ret_specs and "std" in ret_specs
653   assert len(ret_specs["minmax"]) > 0
654   assert len(ret_policy) > 0
655   return (ret_policy, ret_specs)
656
657
658 def TestClusterModifyIPolicy():
659   """gnt-cluster modify --ipolicy-*"""
660   basecmd = ["gnt-cluster", "modify"]
661   (old_policy, old_specs) = _GetClusterIPolicy()
662   for par in ["vcpu-ratio", "spindle-ratio"]:
663     curr_val = float(old_policy[par])
664     test_values = [
665       (True, 1.0),
666       (True, 1.5),
667       (True, 2),
668       (False, "a"),
669       # Restore the old value
670       (True, curr_val),
671       ]
672     for (good, val) in test_values:
673       cmd = basecmd + ["--ipolicy-%s=%s" % (par, val)]
674       AssertCommand(cmd, fail=not good)
675       if good:
676         curr_val = val
677       # Check the affected parameter
678       (eff_policy, eff_specs) = _GetClusterIPolicy()
679       AssertEqual(float(eff_policy[par]), curr_val)
680       # Check everything else
681       AssertEqual(eff_specs, old_specs)
682       for p in eff_policy.keys():
683         if p == par:
684           continue
685         AssertEqual(eff_policy[p], old_policy[p])
686
687   # Disk templates are treated slightly differently
688   par = "disk-templates"
689   disp_str = "allowed disk templates"
690   curr_val = old_policy[disp_str]
691   test_values = [
692     (True, constants.DT_PLAIN),
693     (True, "%s,%s" % (constants.DT_PLAIN, constants.DT_DRBD8)),
694     (False, "thisisnotadisktemplate"),
695     (False, ""),
696     # Restore the old value
697     (True, curr_val.replace(" ", "")),
698     ]
699   for (good, val) in test_values:
700     cmd = basecmd + ["--ipolicy-%s=%s" % (par, val)]
701     AssertCommand(cmd, fail=not good)
702     if good:
703       curr_val = val
704     # Check the affected parameter
705     (eff_policy, eff_specs) = _GetClusterIPolicy()
706     AssertEqual(eff_policy[disp_str].replace(" ", ""), curr_val)
707     # Check everything else
708     AssertEqual(eff_specs, old_specs)
709     for p in eff_policy.keys():
710       if p == disp_str:
711         continue
712       AssertEqual(eff_policy[p], old_policy[p])
713
714
715 def TestClusterSetISpecs(new_specs=None, diff_specs=None, fail=False,
716                          old_values=None):
717   """Change instance specs.
718
719   At most one of new_specs or diff_specs can be specified.
720
721   @type new_specs: dict
722   @param new_specs: new complete specs, in the same format returned by
723       L{_GetClusterIPolicy}
724   @type diff_specs: dict
725   @param diff_specs: partial specs, it can be an incomplete specifications, but
726       if min/max specs are specified, their number must match the number of the
727       existing specs
728   @type fail: bool
729   @param fail: if the change is expected to fail
730   @type old_values: tuple
731   @param old_values: (old_policy, old_specs), as returned by
732       L{_GetClusterIPolicy}
733   @return: same as L{_GetClusterIPolicy}
734
735   """
736   build_cmd = lambda opts: ["gnt-cluster", "modify"] + opts
737   return qa_utils.TestSetISpecs(
738     new_specs=new_specs, diff_specs=diff_specs,
739     get_policy_fn=_GetClusterIPolicy, build_cmd_fn=build_cmd,
740     fail=fail, old_values=old_values)
741
742
743 def TestClusterModifyISpecs():
744   """gnt-cluster modify --specs-*"""
745   params = ["memory-size", "disk-size", "disk-count", "cpu-count", "nic-count"]
746   (cur_policy, cur_specs) = _GetClusterIPolicy()
747   # This test assumes that there is only one min/max bound
748   assert len(cur_specs[constants.ISPECS_MINMAX]) == 1
749   for par in params:
750     test_values = [
751       (True, 0, 4, 12),
752       (True, 4, 4, 12),
753       (True, 4, 12, 12),
754       (True, 4, 4, 4),
755       (False, 4, 0, 12),
756       (False, 4, 16, 12),
757       (False, 4, 4, 0),
758       (False, 12, 4, 4),
759       (False, 12, 4, 0),
760       (False, "a", 4, 12),
761       (False, 0, "a", 12),
762       (False, 0, 4, "a"),
763       # This is to restore the old values
764       (True,
765        cur_specs[constants.ISPECS_MINMAX][0][constants.ISPECS_MIN][par],
766        cur_specs[constants.ISPECS_STD][par],
767        cur_specs[constants.ISPECS_MINMAX][0][constants.ISPECS_MAX][par])
768       ]
769     for (good, mn, st, mx) in test_values:
770       new_vals = {
771         constants.ISPECS_MINMAX: [{
772           constants.ISPECS_MIN: {par: mn},
773           constants.ISPECS_MAX: {par: mx}
774           }],
775         constants.ISPECS_STD: {par: st}
776         }
777       cur_state = (cur_policy, cur_specs)
778       # We update cur_specs, as we've copied the values to restore already
779       (cur_policy, cur_specs) = TestClusterSetISpecs(
780         diff_specs=new_vals, fail=not good, old_values=cur_state)
781
782     # Get the ipolicy command
783     mnode = qa_config.GetMasterNode()
784     initcmd = GetCommandOutput(mnode.primary, "gnt-cluster show-ispecs-cmd")
785     modcmd = ["gnt-cluster", "modify"]
786     opts = initcmd.split()
787     assert opts[0:2] == ["gnt-cluster", "init"]
788     for k in range(2, len(opts) - 1):
789       if opts[k].startswith("--ipolicy-"):
790         assert k + 2 <= len(opts)
791         modcmd.extend(opts[k:k + 2])
792     # Re-apply the ipolicy (this should be a no-op)
793     AssertCommand(modcmd)
794     new_initcmd = GetCommandOutput(mnode.primary, "gnt-cluster show-ispecs-cmd")
795     AssertEqual(initcmd, new_initcmd)
796
797
798 def TestClusterInfo():
799   """gnt-cluster info"""
800   AssertCommand(["gnt-cluster", "info"])
801
802
803 def TestClusterRedistConf():
804   """gnt-cluster redist-conf"""
805   AssertCommand(["gnt-cluster", "redist-conf"])
806
807
808 def TestClusterGetmaster():
809   """gnt-cluster getmaster"""
810   AssertCommand(["gnt-cluster", "getmaster"])
811
812
813 def TestClusterVersion():
814   """gnt-cluster version"""
815   AssertCommand(["gnt-cluster", "version"])
816
817
818 def TestClusterRenewCrypto():
819   """gnt-cluster renew-crypto"""
820   master = qa_config.GetMasterNode()
821
822   # Conflicting options
823   cmd = ["gnt-cluster", "renew-crypto", "--force",
824          "--new-cluster-certificate", "--new-confd-hmac-key"]
825   conflicting = [
826     ["--new-rapi-certificate", "--rapi-certificate=/dev/null"],
827     ["--new-cluster-domain-secret", "--cluster-domain-secret=/dev/null"],
828     ]
829   for i in conflicting:
830     AssertCommand(cmd + i, fail=True)
831
832   # Invalid RAPI certificate
833   cmd = ["gnt-cluster", "renew-crypto", "--force",
834          "--rapi-certificate=/dev/null"]
835   AssertCommand(cmd, fail=True)
836
837   rapi_cert_backup = qa_utils.BackupFile(master.primary,
838                                          pathutils.RAPI_CERT_FILE)
839   try:
840     # Custom RAPI certificate
841     fh = tempfile.NamedTemporaryFile()
842
843     # Ensure certificate doesn't cause "gnt-cluster verify" to complain
844     validity = constants.SSL_CERT_EXPIRATION_WARN * 3
845
846     utils.GenerateSelfSignedSslCert(fh.name, validity=validity)
847
848     tmpcert = qa_utils.UploadFile(master.primary, fh.name)
849     try:
850       AssertCommand(["gnt-cluster", "renew-crypto", "--force",
851                      "--rapi-certificate=%s" % tmpcert])
852     finally:
853       AssertCommand(["rm", "-f", tmpcert])
854
855     # Custom cluster domain secret
856     cds_fh = tempfile.NamedTemporaryFile()
857     cds_fh.write(utils.GenerateSecret())
858     cds_fh.write("\n")
859     cds_fh.flush()
860
861     tmpcds = qa_utils.UploadFile(master.primary, cds_fh.name)
862     try:
863       AssertCommand(["gnt-cluster", "renew-crypto", "--force",
864                      "--cluster-domain-secret=%s" % tmpcds])
865     finally:
866       AssertCommand(["rm", "-f", tmpcds])
867
868     # Normal case
869     AssertCommand(["gnt-cluster", "renew-crypto", "--force",
870                    "--new-cluster-certificate", "--new-confd-hmac-key",
871                    "--new-rapi-certificate", "--new-cluster-domain-secret"])
872
873     # Restore RAPI certificate
874     AssertCommand(["gnt-cluster", "renew-crypto", "--force",
875                    "--rapi-certificate=%s" % rapi_cert_backup])
876   finally:
877     AssertCommand(["rm", "-f", rapi_cert_backup])
878
879
880 def TestClusterBurnin():
881   """Burnin"""
882   master = qa_config.GetMasterNode()
883
884   options = qa_config.get("options", {})
885   disk_template = options.get("burnin-disk-template", constants.DT_DRBD8)
886   parallel = options.get("burnin-in-parallel", False)
887   check_inst = options.get("burnin-check-instances", False)
888   do_rename = options.get("burnin-rename", "")
889   do_reboot = options.get("burnin-reboot", True)
890   reboot_types = options.get("reboot-types", constants.REBOOT_TYPES)
891
892   # Get as many instances as we need
893   instances = []
894   try:
895     try:
896       num = qa_config.get("options", {}).get("burnin-instances", 1)
897       for _ in range(0, num):
898         instances.append(qa_config.AcquireInstance())
899     except qa_error.OutOfInstancesError:
900       print "Not enough instances, continuing anyway."
901
902     if len(instances) < 1:
903       raise qa_error.Error("Burnin needs at least one instance")
904
905     script = qa_utils.UploadFile(master.primary, "../tools/burnin")
906     try:
907       disks = qa_config.GetDiskOptions()
908       # Run burnin
909       cmd = [script,
910              "--os=%s" % qa_config.get("os"),
911              "--minmem-size=%s" % qa_config.get(constants.BE_MINMEM),
912              "--maxmem-size=%s" % qa_config.get(constants.BE_MAXMEM),
913              "--disk-size=%s" % ",".join([d.get("size") for d in disks]),
914              "--disk-growth=%s" % ",".join([d.get("growth") for d in disks]),
915              "--disk-template=%s" % disk_template]
916       if parallel:
917         cmd.append("--parallel")
918         cmd.append("--early-release")
919       if check_inst:
920         cmd.append("--http-check")
921       if do_rename:
922         cmd.append("--rename=%s" % do_rename)
923       if not do_reboot:
924         cmd.append("--no-reboot")
925       else:
926         cmd.append("--reboot-types=%s" % ",".join(reboot_types))
927       cmd += [inst.name for inst in instances]
928       AssertCommand(cmd)
929     finally:
930       AssertCommand(["rm", "-f", script])
931
932   finally:
933     for inst in instances:
934       inst.Release()
935
936
937 def TestClusterMasterFailover():
938   """gnt-cluster master-failover"""
939   master = qa_config.GetMasterNode()
940   failovermaster = qa_config.AcquireNode(exclude=master)
941
942   cmd = ["gnt-cluster", "master-failover"]
943   try:
944     AssertCommand(cmd, node=failovermaster)
945     # Back to original master node
946     AssertCommand(cmd, node=master)
947   finally:
948     failovermaster.Release()
949
950
951 def _NodeQueueDrainFile(node):
952   """Returns path to queue drain file for a node.
953
954   """
955   return qa_utils.MakeNodePath(node, pathutils.JOB_QUEUE_DRAIN_FILE)
956
957
958 def _AssertDrainFile(node, **kwargs):
959   """Checks for the queue drain file.
960
961   """
962   AssertCommand(["test", "-f", _NodeQueueDrainFile(node)], node=node, **kwargs)
963
964
965 def TestClusterMasterFailoverWithDrainedQueue():
966   """gnt-cluster master-failover with drained queue"""
967   master = qa_config.GetMasterNode()
968   failovermaster = qa_config.AcquireNode(exclude=master)
969
970   # Ensure queue is not drained
971   for node in [master, failovermaster]:
972     _AssertDrainFile(node, fail=True)
973
974   # Drain queue on failover master
975   AssertCommand(["touch", _NodeQueueDrainFile(failovermaster)],
976                 node=failovermaster)
977
978   cmd = ["gnt-cluster", "master-failover"]
979   try:
980     _AssertDrainFile(failovermaster)
981     AssertCommand(cmd, node=failovermaster)
982     _AssertDrainFile(master, fail=True)
983     _AssertDrainFile(failovermaster, fail=True)
984
985     # Back to original master node
986     AssertCommand(cmd, node=master)
987   finally:
988     failovermaster.Release()
989
990   # Ensure queue is not drained
991   for node in [master, failovermaster]:
992     _AssertDrainFile(node, fail=True)
993
994
995 def TestClusterCopyfile():
996   """gnt-cluster copyfile"""
997   master = qa_config.GetMasterNode()
998
999   uniqueid = utils.NewUUID()
1000
1001   # Create temporary file
1002   f = tempfile.NamedTemporaryFile()
1003   f.write(uniqueid)
1004   f.flush()
1005   f.seek(0)
1006
1007   # Upload file to master node
1008   testname = qa_utils.UploadFile(master.primary, f.name)
1009   try:
1010     # Copy file to all nodes
1011     AssertCommand(["gnt-cluster", "copyfile", testname])
1012     _CheckFileOnAllNodes(testname, uniqueid)
1013   finally:
1014     _RemoveFileFromAllNodes(testname)
1015
1016
1017 def TestClusterCommand():
1018   """gnt-cluster command"""
1019   uniqueid = utils.NewUUID()
1020   rfile = "/tmp/gnt%s" % utils.NewUUID()
1021   rcmd = utils.ShellQuoteArgs(["echo", "-n", uniqueid])
1022   cmd = utils.ShellQuoteArgs(["gnt-cluster", "command",
1023                               "%s >%s" % (rcmd, rfile)])
1024
1025   try:
1026     AssertCommand(cmd)
1027     _CheckFileOnAllNodes(rfile, uniqueid)
1028   finally:
1029     _RemoveFileFromAllNodes(rfile)
1030
1031
1032 def TestClusterDestroy():
1033   """gnt-cluster destroy"""
1034   AssertCommand(["gnt-cluster", "destroy", "--yes-do-it"])
1035
1036
1037 def TestClusterRepairDiskSizes():
1038   """gnt-cluster repair-disk-sizes"""
1039   AssertCommand(["gnt-cluster", "repair-disk-sizes"])
1040
1041
1042 def TestSetExclStorCluster(newvalue):
1043   """Set the exclusive_storage node parameter at the cluster level.
1044
1045   @type newvalue: bool
1046   @param newvalue: New value of exclusive_storage
1047   @rtype: bool
1048   @return: The old value of exclusive_storage
1049
1050   """
1051   es_path = ["Default node parameters", "exclusive_storage"]
1052   oldvalue = _GetClusterField(es_path)
1053   AssertCommand(["gnt-cluster", "modify", "--node-parameters",
1054                  "exclusive_storage=%s" % newvalue])
1055   effvalue = _GetClusterField(es_path)
1056   if effvalue != newvalue:
1057     raise qa_error.Error("exclusive_storage has the wrong value: %s instead"
1058                          " of %s" % (effvalue, newvalue))
1059   qa_config.SetExclusiveStorage(newvalue)
1060   return oldvalue
1061
1062
1063 def TestExclStorSharedPv(node):
1064   """cluster-verify reports LVs that share the same PV with exclusive_storage.
1065
1066   """
1067   vgname = qa_config.get("vg-name", constants.DEFAULT_VG)
1068   lvname1 = _QA_LV_PREFIX + "vol1"
1069   lvname2 = _QA_LV_PREFIX + "vol2"
1070   node_name = node.primary
1071   AssertCommand(["lvcreate", "-L1G", "-n", lvname1, vgname], node=node_name)
1072   AssertClusterVerify(fail=True, errors=[constants.CV_ENODEORPHANLV])
1073   AssertCommand(["lvcreate", "-L1G", "-n", lvname2, vgname], node=node_name)
1074   AssertClusterVerify(fail=True, errors=[constants.CV_ENODELVM,
1075                                          constants.CV_ENODEORPHANLV])
1076   AssertCommand(["lvremove", "-f", "/".join([vgname, lvname1])], node=node_name)
1077   AssertCommand(["lvremove", "-f", "/".join([vgname, lvname2])], node=node_name)
1078   AssertClusterVerify()