4 # Copyright (C) 2007, 2010, 2011, 2012, 2013 Google Inc.
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.
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.
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
22 """Cluster related QA tests.
30 from ganeti import constants
31 from ganeti import compat
32 from ganeti import utils
33 from ganeti import pathutils
40 from qa_utils import AssertEqual, AssertCommand, GetCommandOutput
43 # Prefix for LVM volumes created by QA code during tests
46 #: cluster verify command
47 _CLUSTER_VERIFY = ["gnt-cluster", "verify"]
50 def _RemoveFileFromAllNodes(filename):
51 """Removes a file from all nodes.
54 for node in qa_config.get("nodes"):
55 AssertCommand(["rm", "-f", filename], node=node)
58 def _CheckFileOnAllNodes(filename, content):
59 """Verifies the content of the given file on all nodes.
62 cmd = utils.ShellQuoteArgs(["cat", filename])
63 for node in qa_config.get("nodes"):
64 AssertEqual(qa_utils.GetCommandOutput(node.primary, cmd), content)
67 def _GetClusterField(field_path):
68 """Get the value of a cluster field.
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
77 assert isinstance(field_path, list)
79 ret = qa_utils.GetObjectInfo(["gnt-cluster", "info"])
80 for key in field_path:
85 # Cluster-verify errors (date, "ERROR", then error code)
86 _CVERROR_RE = re.compile(r"^[\w\s:]+\s+- (ERROR|WARNING):([A-Z0-9_-]+):")
89 def _GetCVErrorCodes(cvout):
92 for l in cvout.splitlines():
93 m = _CVERROR_RE.match(l)
99 elif etype == "WARNING":
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)))
112 def AssertClusterVerify(fail=False, errors=None, warnings=None):
113 """Run cluster-verify and check the result
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.
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)
132 _CheckVerifyErrors(act_errs, errors, "error")
134 _CheckVerifyErrors(act_warns, warnings, "warning")
136 AssertCommand(cvcmd, fail=fail, node=mnode)
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",
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)
153 def TestClusterInit(rapi_user, rapi_secret):
154 """gnt-cluster init"""
155 master = qa_config.GetMasterNode()
157 rapi_users_path = qa_utils.MakeNodePath(master, pathutils.RAPI_USERS_FILE)
158 rapi_dir = os.path.dirname(rapi_users_path)
160 # First create the RAPI credentials
161 fh = tempfile.NamedTemporaryFile()
163 fh.write("%s %s write\n" % (rapi_user, rapi_secret))
166 tmpru = qa_utils.UploadFile(master.primary, fh.name)
168 AssertCommand(["mkdir", "-p", rapi_dir])
169 AssertCommand(["mv", tmpru, rapi_users_path])
171 AssertCommand(["rm", "-f", tmpru])
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())
184 for spec_type in ("mem-size", "disk-size", "disk-count", "cpu-count",
186 for spec_val in ("min", "max", "std"):
187 spec = qa_config.get("ispec_%s_%s" %
188 (spec_type.replace("-", "_"), spec_val), None)
190 cmd.append("--specs-%s=%s=%d" % (spec_type, spec_val, spec))
193 cmd.append("--secondary-ip=%s" % master.secondary)
195 if utils.IsLvmEnabled(qa_config.GetEnabledDiskTemplates()):
196 vgname = qa_config.get("vg-name", constants.DEFAULT_VG)
198 cmd.append("--vg-name=%s" % vgname)
200 raise qa_error.Error("Please specify a volume group if you enable"
201 " lvm-based disk templates in the QA.")
203 master_netdev = qa_config.get("master-netdev", None)
205 cmd.append("--master-netdev=%s" % master_netdev)
207 nicparams = qa_config.get("default-nicparams", None)
209 cmd.append("--nic-parameters=%s" %
210 ",".join(utils.FormatKeyValue(nicparams)))
212 # Cluster value of the exclusive-storage node parameter
213 e_s = qa_config.get("exclusive-storage")
215 cmd.extend(["--node-parameters", "exclusive_storage=%s" % e_s])
218 qa_config.SetExclusiveStorage(e_s)
220 extra_args = qa_config.get("cluster-init-args")
222 cmd.extend(extra_args)
224 cmd.append(qa_config.get("name"))
228 cmd = ["gnt-cluster", "modify"]
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", "")
237 cmd.extend(["-B", bep])
243 osp = qa_config.get("os-parameters", {})
244 for k, v in osp.items():
245 AssertCommand(["gnt-os", "modify", "-O", v, k])
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])
254 def TestClusterRename():
255 """gnt-cluster rename"""
256 cmd = ["gnt-cluster", "rename", "-f"]
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')
265 cmd + [rename_target],
267 cmd + [original_name],
273 def TestClusterOob():
274 """out-of-band framework"""
275 oob_path_exists = "/tmp/ganeti-qa-oob-does-exist-%s" % utils.NewUUID()
277 AssertCommand(_CLUSTER_VERIFY)
278 AssertCommand(["gnt-cluster", "modify", "--node-parameters",
279 "oob_program=/tmp/ganeti-qa-oob-does-not-exist-%s" %
282 AssertCommand(_CLUSTER_VERIFY, fail=True)
284 AssertCommand(["touch", oob_path_exists])
285 AssertCommand(["chmod", "0400", oob_path_exists])
286 AssertCommand(["gnt-cluster", "copyfile", oob_path_exists])
289 AssertCommand(["gnt-cluster", "modify", "--node-parameters",
290 "oob_program=%s" % oob_path_exists])
292 AssertCommand(_CLUSTER_VERIFY, fail=True)
294 AssertCommand(["chmod", "0500", oob_path_exists])
295 AssertCommand(["gnt-cluster", "copyfile", oob_path_exists])
297 AssertCommand(_CLUSTER_VERIFY)
299 AssertCommand(["gnt-cluster", "command", "rm", oob_path_exists])
301 AssertCommand(["gnt-cluster", "modify", "--node-parameters",
305 def TestClusterEpo():
306 """gnt-cluster epo"""
307 master = qa_config.GetMasterNode()
309 # Assert that OOB is unavailable for all nodes
310 result_output = GetCommandOutput(master.primary,
311 "gnt-node list --verbose --no-headers -o"
313 AssertEqual(compat.all(powered == "(unavail)"
314 for powered in result_output.splitlines()), True)
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)
321 # Unless --all is given master is not allowed to be in the list
322 AssertCommand(["gnt-cluster", "epo", "-f", master.primary], fail=True)
324 # This shouldn't fail
325 AssertCommand(["gnt-cluster", "epo", "-f", "--all"])
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)
334 # Now start everything again
335 AssertCommand(["gnt-cluster", "epo", "--on", "-f", "--all"])
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)
344 def TestClusterVerify():
345 """gnt-cluster verify"""
346 AssertCommand(_CLUSTER_VERIFY)
347 AssertCommand(["gnt-cluster", "verify-disks"])
351 """gnt-debug test-jobqueue"""
352 AssertCommand(["gnt-debug", "test-jobqueue"])
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"])
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])
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),
384 AssertCommand(cmd, fail=fail)
387 def TestClusterModifyEmpty():
388 """gnt-cluster modify"""
389 AssertCommand(["gnt-cluster", "modify"], fail=True)
392 def TestClusterModifyDisk():
393 """gnt-cluster modify -D"""
394 for param in _FAIL_PARAMS:
395 AssertCommand(["gnt-cluster", "modify", "-D", param], fail=True)
398 def TestClusterModifyDiskTemplates():
399 """gnt-cluster modify --enabled-disk-templates=..."""
400 enabled_disk_templates = qa_config.GetEnabledDiskTemplates()
401 default_disk_template = qa_config.GetDefaultDiskTemplate()
403 _TestClusterModifyDiskTemplatesArguments(default_disk_template,
404 enabled_disk_templates)
405 _TestClusterModifyDiskTemplatesVgName(enabled_disk_templates)
407 _RestoreEnabledDiskTemplates()
408 nodes = qa_config.AcquireManyNodes(2)
410 instance_template = enabled_disk_templates[0]
411 instance = qa_instance.CreateInstanceByDiskTemplate(nodes, instance_template)
413 _TestClusterModifyUnusedDiskTemplate(instance_template)
414 _TestClusterModifyUsedDiskTemplate(instance_template,
415 enabled_disk_templates)
417 qa_instance.TestInstanceRemove(instance)
418 _RestoreEnabledDiskTemplates()
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
428 cmd = ["gnt-cluster", "modify", "--enabled-disk-templates=%s" %
429 ",".join(qa_config.GetEnabledDiskTemplates())]
431 if utils.IsLvmEnabled(qa_config.GetEnabledDiskTemplates()):
432 vgname = qa_config.get("vg-name", constants.DEFAULT_VG)
433 cmd.append("--vg-name=%s" % vgname)
435 AssertCommand(cmd, fail=False)
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
445 _RestoreEnabledDiskTemplates()
448 AssertCommand(["gnt-cluster", "modify",
449 "--enabled-disk-templates=pinkbunny"],
452 # duplicate entries do no harm
454 ["gnt-cluster", "modify",
455 "--enabled-disk-templates=%s,%s" %
456 (default_disk_template, default_disk_template)],
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],
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)],
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.
484 if not utils.IsLvmEnabled(enabled_disk_templates):
485 # These tests only make sense if lvm is enabled for QA
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]
497 # If no non-lvm disk template is available for QA, choose 'diskless' and
499 non_lvm_template = constants.ST_DISKLESS
501 vgname = qa_config.get("vg-name", constants.DEFAULT_VG)
503 # Clean start: unset volume group name, disable lvm storage
505 ["gnt-cluster", "modify",
506 "--enabled-disk-templates=%s" % non_lvm_template,
510 # Try to enable lvm, when no volume group is given
512 ["gnt-cluster", "modify",
513 "--enabled-disk-templates=%s" % lvm_template],
516 # Set volume group, with lvm still disabled: just a warning
517 AssertCommand(["gnt-cluster", "modify", "--vg-name=%s" % vgname], fail=False)
519 # Try unsetting vg name and enabling lvm at the same time
521 ["gnt-cluster", "modify",
522 "--enabled-disk-templates=%s" % lvm_template,
526 # Enable lvm with vg name present
528 ["gnt-cluster", "modify",
529 "--enabled-disk-templates=%s" % lvm_template],
532 # Try unsetting vg name with lvm still enabled
533 AssertCommand(["gnt-cluster", "modify", "--vg-name="], fail=True)
535 # Disable lvm with vg name still set
537 ["gnt-cluster", "modify", "--enabled-disk-templates=%s" % non_lvm_template],
540 # Try unsetting vg name with lvm disabled
541 AssertCommand(["gnt-cluster", "modify", "--vg-name="], fail=False)
543 # Set vg name and enable lvm at the same time
545 ["gnt-cluster", "modify",
546 "--enabled-disk-templates=%s" % lvm_template,
547 "--vg-name=%s" % vgname],
550 # Unset vg name and disable lvm at the same time
552 ["gnt-cluster", "modify",
553 "--enabled-disk-templates=%s" % non_lvm_template,
557 _RestoreEnabledDiskTemplates()
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.
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]))
575 ["gnt-cluster", "modify",
576 "--enabled-disk-templates=%s" %
577 ",".join(new_disk_templates)],
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()))
589 ["gnt-cluster", "modify",
590 "--enabled-disk-templates=%s" %
591 ",".join(all_disk_templates)],
593 new_disk_templates = [instance_template]
595 ["gnt-cluster", "modify",
596 "--enabled-disk-templates=%s" %
597 ",".join(new_disk_templates)],
601 def TestClusterModifyBe():
602 """gnt-cluster modify -B"""
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$'"]),
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$'"]),
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$'"]),
629 AssertCommand(cmd, fail=fail)
631 # redo the original-requested BE parameters, if any
632 bep = qa_config.get("backend-parameters", "")
634 AssertCommand(["gnt-cluster", "modify", "-B", bep])
637 def _GetClusterIPolicy():
638 """Return the run-time values of the cluster-level instance policy.
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)
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)
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)
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])
669 # Restore the old value
672 for (good, val) in test_values:
673 cmd = basecmd + ["--ipolicy-%s=%s" % (par, val)]
674 AssertCommand(cmd, fail=not good)
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():
685 AssertEqual(eff_policy[p], old_policy[p])
687 # Disk templates are treated slightly differently
688 par = "disk-templates"
689 disp_str = "allowed disk templates"
690 curr_val = old_policy[disp_str]
692 (True, constants.DT_PLAIN),
693 (True, "%s,%s" % (constants.DT_PLAIN, constants.DT_DRBD8)),
694 (False, "thisisnotadisktemplate"),
696 # Restore the old value
697 (True, curr_val.replace(" ", "")),
699 for (good, val) in test_values:
700 cmd = basecmd + ["--ipolicy-%s=%s" % (par, val)]
701 AssertCommand(cmd, fail=not good)
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():
712 AssertEqual(eff_policy[p], old_policy[p])
715 def TestClusterSetISpecs(new_specs=None, diff_specs=None, fail=False,
717 """Change instance specs.
719 At most one of new_specs or diff_specs can be specified.
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
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}
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)
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
763 # This is to restore the old values
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])
769 for (good, mn, st, mx) in test_values:
771 constants.ISPECS_MINMAX: [{
772 constants.ISPECS_MIN: {par: mn},
773 constants.ISPECS_MAX: {par: mx}
775 constants.ISPECS_STD: {par: st}
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)
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)
798 def TestClusterInfo():
799 """gnt-cluster info"""
800 AssertCommand(["gnt-cluster", "info"])
803 def TestClusterRedistConf():
804 """gnt-cluster redist-conf"""
805 AssertCommand(["gnt-cluster", "redist-conf"])
808 def TestClusterGetmaster():
809 """gnt-cluster getmaster"""
810 AssertCommand(["gnt-cluster", "getmaster"])
813 def TestClusterVersion():
814 """gnt-cluster version"""
815 AssertCommand(["gnt-cluster", "version"])
818 def TestClusterRenewCrypto():
819 """gnt-cluster renew-crypto"""
820 master = qa_config.GetMasterNode()
822 # Conflicting options
823 cmd = ["gnt-cluster", "renew-crypto", "--force",
824 "--new-cluster-certificate", "--new-confd-hmac-key"]
826 ["--new-rapi-certificate", "--rapi-certificate=/dev/null"],
827 ["--new-cluster-domain-secret", "--cluster-domain-secret=/dev/null"],
829 for i in conflicting:
830 AssertCommand(cmd + i, fail=True)
832 # Invalid RAPI certificate
833 cmd = ["gnt-cluster", "renew-crypto", "--force",
834 "--rapi-certificate=/dev/null"]
835 AssertCommand(cmd, fail=True)
837 rapi_cert_backup = qa_utils.BackupFile(master.primary,
838 pathutils.RAPI_CERT_FILE)
840 # Custom RAPI certificate
841 fh = tempfile.NamedTemporaryFile()
843 # Ensure certificate doesn't cause "gnt-cluster verify" to complain
844 validity = constants.SSL_CERT_EXPIRATION_WARN * 3
846 utils.GenerateSelfSignedSslCert(fh.name, validity=validity)
848 tmpcert = qa_utils.UploadFile(master.primary, fh.name)
850 AssertCommand(["gnt-cluster", "renew-crypto", "--force",
851 "--rapi-certificate=%s" % tmpcert])
853 AssertCommand(["rm", "-f", tmpcert])
855 # Custom cluster domain secret
856 cds_fh = tempfile.NamedTemporaryFile()
857 cds_fh.write(utils.GenerateSecret())
861 tmpcds = qa_utils.UploadFile(master.primary, cds_fh.name)
863 AssertCommand(["gnt-cluster", "renew-crypto", "--force",
864 "--cluster-domain-secret=%s" % tmpcds])
866 AssertCommand(["rm", "-f", tmpcds])
869 AssertCommand(["gnt-cluster", "renew-crypto", "--force",
870 "--new-cluster-certificate", "--new-confd-hmac-key",
871 "--new-rapi-certificate", "--new-cluster-domain-secret"])
873 # Restore RAPI certificate
874 AssertCommand(["gnt-cluster", "renew-crypto", "--force",
875 "--rapi-certificate=%s" % rapi_cert_backup])
877 AssertCommand(["rm", "-f", rapi_cert_backup])
880 def TestClusterBurnin():
882 master = qa_config.GetMasterNode()
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)
892 # Get as many instances as we need
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."
902 if len(instances) < 1:
903 raise qa_error.Error("Burnin needs at least one instance")
905 script = qa_utils.UploadFile(master.primary, "../tools/burnin")
907 disks = qa_config.GetDiskOptions()
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]
917 cmd.append("--parallel")
918 cmd.append("--early-release")
920 cmd.append("--http-check")
922 cmd.append("--rename=%s" % do_rename)
924 cmd.append("--no-reboot")
926 cmd.append("--reboot-types=%s" % ",".join(reboot_types))
927 cmd += [inst.name for inst in instances]
930 AssertCommand(["rm", "-f", script])
933 for inst in instances:
937 def TestClusterMasterFailover():
938 """gnt-cluster master-failover"""
939 master = qa_config.GetMasterNode()
940 failovermaster = qa_config.AcquireNode(exclude=master)
942 cmd = ["gnt-cluster", "master-failover"]
944 AssertCommand(cmd, node=failovermaster)
945 # Back to original master node
946 AssertCommand(cmd, node=master)
948 failovermaster.Release()
951 def _NodeQueueDrainFile(node):
952 """Returns path to queue drain file for a node.
955 return qa_utils.MakeNodePath(node, pathutils.JOB_QUEUE_DRAIN_FILE)
958 def _AssertDrainFile(node, **kwargs):
959 """Checks for the queue drain file.
962 AssertCommand(["test", "-f", _NodeQueueDrainFile(node)], node=node, **kwargs)
965 def TestClusterMasterFailoverWithDrainedQueue():
966 """gnt-cluster master-failover with drained queue"""
967 master = qa_config.GetMasterNode()
968 failovermaster = qa_config.AcquireNode(exclude=master)
970 # Ensure queue is not drained
971 for node in [master, failovermaster]:
972 _AssertDrainFile(node, fail=True)
974 # Drain queue on failover master
975 AssertCommand(["touch", _NodeQueueDrainFile(failovermaster)],
978 cmd = ["gnt-cluster", "master-failover"]
980 _AssertDrainFile(failovermaster)
981 AssertCommand(cmd, node=failovermaster)
982 _AssertDrainFile(master, fail=True)
983 _AssertDrainFile(failovermaster, fail=True)
985 # Back to original master node
986 AssertCommand(cmd, node=master)
988 failovermaster.Release()
990 # Ensure queue is not drained
991 for node in [master, failovermaster]:
992 _AssertDrainFile(node, fail=True)
995 def TestClusterCopyfile():
996 """gnt-cluster copyfile"""
997 master = qa_config.GetMasterNode()
999 uniqueid = utils.NewUUID()
1001 # Create temporary file
1002 f = tempfile.NamedTemporaryFile()
1007 # Upload file to master node
1008 testname = qa_utils.UploadFile(master.primary, f.name)
1010 # Copy file to all nodes
1011 AssertCommand(["gnt-cluster", "copyfile", testname])
1012 _CheckFileOnAllNodes(testname, uniqueid)
1014 _RemoveFileFromAllNodes(testname)
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)])
1027 _CheckFileOnAllNodes(rfile, uniqueid)
1029 _RemoveFileFromAllNodes(rfile)
1032 def TestClusterDestroy():
1033 """gnt-cluster destroy"""
1034 AssertCommand(["gnt-cluster", "destroy", "--yes-do-it"])
1037 def TestClusterRepairDiskSizes():
1038 """gnt-cluster repair-disk-sizes"""
1039 AssertCommand(["gnt-cluster", "repair-disk-sizes"])
1042 def TestSetExclStorCluster(newvalue):
1043 """Set the exclusive_storage node parameter at the cluster level.
1045 @type newvalue: bool
1046 @param newvalue: New value of exclusive_storage
1048 @return: The old value of exclusive_storage
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)
1063 def TestExclStorSharedPv(node):
1064 """cluster-verify reports LVs that share the same PV with exclusive_storage.
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()