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
41 from qa_utils import AssertEqual, AssertCommand, GetCommandOutput
44 # Prefix for LVM volumes created by QA code during tests
47 #: cluster verify command
48 _CLUSTER_VERIFY = ["gnt-cluster", "verify"]
51 def _RemoveFileFromAllNodes(filename):
52 """Removes a file from all nodes.
55 for node in qa_config.get("nodes"):
56 AssertCommand(["rm", "-f", filename], node=node)
59 def _CheckFileOnAllNodes(filename, content):
60 """Verifies the content of the given file on all nodes.
63 cmd = utils.ShellQuoteArgs(["cat", filename])
64 for node in qa_config.get("nodes"):
65 AssertEqual(qa_utils.GetCommandOutput(node.primary, cmd), content)
68 def _GetClusterField(field_path):
69 """Get the value of a cluster field.
71 @type field_path: list of strings
72 @param field_path: Names of the groups/fields to navigate to get the desired
73 value, e.g. C{["Default node parameters", "oob_program"]}
74 @return: The effective value of the field (the actual type depends on the
78 assert isinstance(field_path, list)
80 ret = qa_utils.GetObjectInfo(["gnt-cluster", "info"])
81 for key in field_path:
86 # Cluster-verify errors (date, "ERROR", then error code)
87 _CVERROR_RE = re.compile(r"^[\w\s:]+\s+- (ERROR|WARNING):([A-Z0-9_-]+):")
90 def _GetCVErrorCodes(cvout):
93 for l in cvout.splitlines():
94 m = _CVERROR_RE.match(l)
100 elif etype == "WARNING":
105 def _CheckVerifyErrors(actual, expected, etype):
106 exp_codes = compat.UniqueFrozenset(e for (_, e, _) in expected)
107 if not actual.issuperset(exp_codes):
108 missing = exp_codes.difference(actual)
109 raise qa_error.Error("Cluster-verify didn't return these expected"
110 " %ss: %s" % (etype, utils.CommaJoin(missing)))
113 def AssertClusterVerify(fail=False, errors=None, warnings=None):
114 """Run cluster-verify and check the result
117 @param fail: if cluster-verify is expected to fail instead of succeeding
118 @type errors: list of tuples
119 @param errors: List of CV_XXX errors that are expected; if specified, all the
120 errors listed must appear in cluster-verify output. A non-empty value
121 implies C{fail=True}.
122 @type warnings: list of tuples
123 @param warnings: Same as C{errors} but for warnings.
126 cvcmd = "gnt-cluster verify"
127 mnode = qa_config.GetMasterNode()
128 if errors or warnings:
129 cvout = GetCommandOutput(mnode.primary, cvcmd + " --error-codes",
130 fail=(fail or errors))
131 (act_errs, act_warns) = _GetCVErrorCodes(cvout)
133 _CheckVerifyErrors(act_errs, errors, "error")
135 _CheckVerifyErrors(act_warns, warnings, "warning")
137 AssertCommand(cvcmd, fail=fail, node=mnode)
140 # data for testing failures due to bad keys/values for disk parameters
141 _FAIL_PARAMS = ["nonexistent:resync-rate=1",
142 "drbd:nonexistent=1",
143 "drbd:resync-rate=invalid",
147 def TestClusterInitDisk():
148 """gnt-cluster init -D"""
149 name = qa_config.get("name")
150 for param in _FAIL_PARAMS:
151 AssertCommand(["gnt-cluster", "init", "-D", param, name], fail=True)
154 def TestClusterInit(rapi_user, rapi_secret):
155 """gnt-cluster init"""
156 master = qa_config.GetMasterNode()
158 rapi_users_path = qa_utils.MakeNodePath(master, pathutils.RAPI_USERS_FILE)
159 rapi_dir = os.path.dirname(rapi_users_path)
161 # First create the RAPI credentials
162 fh = tempfile.NamedTemporaryFile()
164 fh.write("%s %s write\n" % (rapi_user, rapi_secret))
167 tmpru = qa_utils.UploadFile(master.primary, fh.name)
169 AssertCommand(["mkdir", "-p", rapi_dir])
170 AssertCommand(["mv", tmpru, rapi_users_path])
172 AssertCommand(["rm", "-f", tmpru])
177 enabled_disk_templates = qa_config.GetEnabledDiskTemplates()
179 "gnt-cluster", "init",
180 "--primary-ip-version=%d" % qa_config.get("primary_ip_version", 4),
181 "--enabled-hypervisors=%s" % ",".join(qa_config.GetEnabledHypervisors()),
182 "--enabled-disk-templates=%s" %
183 ",".join(enabled_disk_templates),
185 if constants.DT_FILE in enabled_disk_templates:
187 "--file-storage-dir=%s" %
188 qa_config.get("file-storage-dir", pathutils.DEFAULT_FILE_STORAGE_DIR))
190 for spec_type in ("mem-size", "disk-size", "disk-count", "cpu-count",
192 for spec_val in ("min", "max", "std"):
193 spec = qa_config.get("ispec_%s_%s" %
194 (spec_type.replace("-", "_"), spec_val), None)
196 cmd.append("--specs-%s=%s=%d" % (spec_type, spec_val, spec))
199 cmd.append("--secondary-ip=%s" % master.secondary)
201 if utils.IsLvmEnabled(qa_config.GetEnabledDiskTemplates()):
202 vgname = qa_config.get("vg-name", constants.DEFAULT_VG)
204 cmd.append("--vg-name=%s" % vgname)
206 raise qa_error.Error("Please specify a volume group if you enable"
207 " lvm-based disk templates in the QA.")
209 master_netdev = qa_config.get("master-netdev", None)
211 cmd.append("--master-netdev=%s" % master_netdev)
213 nicparams = qa_config.get("default-nicparams", None)
215 cmd.append("--nic-parameters=%s" %
216 ",".join(utils.FormatKeyValue(nicparams)))
218 # Cluster value of the exclusive-storage node parameter
219 e_s = qa_config.get("exclusive-storage")
221 cmd.extend(["--node-parameters", "exclusive_storage=%s" % e_s])
224 qa_config.SetExclusiveStorage(e_s)
226 extra_args = qa_config.get("cluster-init-args")
228 cmd.extend(extra_args)
230 cmd.append(qa_config.get("name"))
234 cmd = ["gnt-cluster", "modify"]
236 # hypervisor parameter modifications
237 hvp = qa_config.get("hypervisor-parameters", {})
238 for k, v in hvp.items():
239 cmd.extend(["-H", "%s:%s" % (k, v)])
240 # backend parameter modifications
241 bep = qa_config.get("backend-parameters", "")
243 cmd.extend(["-B", bep])
249 osp = qa_config.get("os-parameters", {})
250 for k, v in osp.items():
251 AssertCommand(["gnt-os", "modify", "-O", v, k])
253 # OS hypervisor parameters
254 os_hvp = qa_config.get("os-hvp", {})
255 for os_name in os_hvp:
256 for hv, hvp in os_hvp[os_name].items():
257 AssertCommand(["gnt-os", "modify", "-H", "%s:%s" % (hv, hvp), os_name])
260 def TestClusterRename():
261 """gnt-cluster rename"""
262 cmd = ["gnt-cluster", "rename", "-f"]
264 original_name = qa_config.get("name")
265 rename_target = qa_config.get("rename", None)
266 if rename_target is None:
267 print qa_utils.FormatError('"rename" entry is missing')
271 cmd + [rename_target],
273 cmd + [original_name],
279 def TestClusterOob():
280 """out-of-band framework"""
281 oob_path_exists = "/tmp/ganeti-qa-oob-does-exist-%s" % utils.NewUUID()
283 AssertCommand(_CLUSTER_VERIFY)
284 AssertCommand(["gnt-cluster", "modify", "--node-parameters",
285 "oob_program=/tmp/ganeti-qa-oob-does-not-exist-%s" %
288 AssertCommand(_CLUSTER_VERIFY, fail=True)
290 AssertCommand(["touch", oob_path_exists])
291 AssertCommand(["chmod", "0400", oob_path_exists])
292 AssertCommand(["gnt-cluster", "copyfile", oob_path_exists])
295 AssertCommand(["gnt-cluster", "modify", "--node-parameters",
296 "oob_program=%s" % oob_path_exists])
298 AssertCommand(_CLUSTER_VERIFY, fail=True)
300 AssertCommand(["chmod", "0500", oob_path_exists])
301 AssertCommand(["gnt-cluster", "copyfile", oob_path_exists])
303 AssertCommand(_CLUSTER_VERIFY)
305 AssertCommand(["gnt-cluster", "command", "rm", oob_path_exists])
307 AssertCommand(["gnt-cluster", "modify", "--node-parameters",
311 def TestClusterEpo():
312 """gnt-cluster epo"""
313 master = qa_config.GetMasterNode()
315 # Assert that OOB is unavailable for all nodes
316 result_output = GetCommandOutput(master.primary,
317 "gnt-node list --verbose --no-headers -o"
319 AssertEqual(compat.all(powered == "(unavail)"
320 for powered in result_output.splitlines()), True)
323 AssertCommand(["gnt-cluster", "epo", "--groups", "--all"], fail=True)
324 # --all doesn't expect arguments
325 AssertCommand(["gnt-cluster", "epo", "--all", "some_arg"], fail=True)
327 # Unless --all is given master is not allowed to be in the list
328 AssertCommand(["gnt-cluster", "epo", "-f", master.primary], fail=True)
330 # This shouldn't fail
331 AssertCommand(["gnt-cluster", "epo", "-f", "--all"])
333 # All instances should have been stopped now
334 result_output = GetCommandOutput(master.primary,
335 "gnt-instance list --no-headers -o status")
336 # ERROR_down because the instance is stopped but not recorded as such
337 AssertEqual(compat.all(status == "ERROR_down"
338 for status in result_output.splitlines()), True)
340 # Now start everything again
341 AssertCommand(["gnt-cluster", "epo", "--on", "-f", "--all"])
343 # All instances should have been started now
344 result_output = GetCommandOutput(master.primary,
345 "gnt-instance list --no-headers -o status")
346 AssertEqual(compat.all(status == "running"
347 for status in result_output.splitlines()), True)
350 def TestClusterVerify():
351 """gnt-cluster verify"""
352 AssertCommand(_CLUSTER_VERIFY)
353 AssertCommand(["gnt-cluster", "verify-disks"])
356 def TestClusterVerifyDisksBrokenDRBD(instance, inst_nodes):
357 """gnt-cluster verify-disks with broken DRBD"""
358 qa_daemon.TestPauseWatcher()
361 info = qa_instance.GetInstanceInfo(instance.name)
362 snode = inst_nodes[1]
363 for idx, minor in enumerate(info["drbd-minors"][snode.primary]):
366 "(drbdsetup %d down >/dev/null 2>&1;" \
367 " drbdsetup down resource%d >/dev/null 2>&1) || /bin/true" % \
371 "(drbdsetup %d detach >/dev/null 2>&1;" \
372 " drbdsetup detach %d >/dev/null 2>&1) || /bin/true" % \
374 AssertCommand(break_drbd_cmd, node=snode)
376 verify_output = GetCommandOutput(qa_config.GetMasterNode().primary,
377 "gnt-cluster verify-disks")
378 activation_msg = "Activating disks for instance '%s'" % instance.name
379 if activation_msg not in verify_output:
380 raise qa_error.Error("gnt-cluster verify-disks did not activate broken"
381 " DRBD disks:\n%s" % verify_output)
383 verify_output = GetCommandOutput(qa_config.GetMasterNode().primary,
384 "gnt-cluster verify-disks")
385 if activation_msg in verify_output:
386 raise qa_error.Error("gnt-cluster verify-disks wants to activate broken"
387 " DRBD disks on second attempt:\n%s" % verify_output)
389 AssertCommand(_CLUSTER_VERIFY)
391 qa_daemon.TestResumeWatcher()
395 """gnt-debug test-jobqueue"""
396 AssertCommand(["gnt-debug", "test-jobqueue"])
400 """gnt-debug delay"""
401 AssertCommand(["gnt-debug", "delay", "1"])
402 AssertCommand(["gnt-debug", "delay", "--no-master", "1"])
403 AssertCommand(["gnt-debug", "delay", "--no-master",
404 "-n", node.primary, "1"])
407 def TestClusterReservedLvs():
408 """gnt-cluster reserved lvs"""
409 vgname = qa_config.get("vg-name", constants.DEFAULT_VG)
410 lvname = _QA_LV_PREFIX + "test"
411 lvfullname = "/".join([vgname, lvname])
413 (False, _CLUSTER_VERIFY),
414 (False, ["gnt-cluster", "modify", "--reserved-lvs", ""]),
415 (False, ["lvcreate", "-L1G", "-n", lvname, vgname]),
416 (True, _CLUSTER_VERIFY),
417 (False, ["gnt-cluster", "modify", "--reserved-lvs",
418 "%s,.*/other-test" % lvfullname]),
419 (False, _CLUSTER_VERIFY),
420 (False, ["gnt-cluster", "modify", "--reserved-lvs",
421 ".*/%s.*" % _QA_LV_PREFIX]),
422 (False, _CLUSTER_VERIFY),
423 (False, ["gnt-cluster", "modify", "--reserved-lvs", ""]),
424 (True, _CLUSTER_VERIFY),
425 (False, ["lvremove", "-f", lvfullname]),
426 (False, _CLUSTER_VERIFY),
428 AssertCommand(cmd, fail=fail)
431 def TestClusterModifyEmpty():
432 """gnt-cluster modify"""
433 AssertCommand(["gnt-cluster", "modify"], fail=True)
436 def TestClusterModifyDisk():
437 """gnt-cluster modify -D"""
438 for param in _FAIL_PARAMS:
439 AssertCommand(["gnt-cluster", "modify", "-D", param], fail=True)
442 def TestClusterModifyDiskTemplates():
443 """gnt-cluster modify --enabled-disk-templates=..."""
444 enabled_disk_templates = qa_config.GetEnabledDiskTemplates()
445 default_disk_template = qa_config.GetDefaultDiskTemplate()
447 _TestClusterModifyDiskTemplatesArguments(default_disk_template,
448 enabled_disk_templates)
449 _TestClusterModifyDiskTemplatesVgName(enabled_disk_templates)
451 _RestoreEnabledDiskTemplates()
452 nodes = qa_config.AcquireManyNodes(2)
454 instance_template = enabled_disk_templates[0]
455 instance = qa_instance.CreateInstanceByDiskTemplate(nodes, instance_template)
457 _TestClusterModifyUnusedDiskTemplate(instance_template)
458 _TestClusterModifyUsedDiskTemplate(instance_template,
459 enabled_disk_templates)
461 qa_instance.TestInstanceRemove(instance)
462 _RestoreEnabledDiskTemplates()
465 def _RestoreEnabledDiskTemplates():
466 """Sets the list of enabled disk templates back to the list of enabled disk
467 templates from the QA configuration. This can be used to make sure that
468 the tests that modify the list of disk templates do not interfere with
472 cmd = ["gnt-cluster", "modify", "--enabled-disk-templates=%s" %
473 ",".join(qa_config.GetEnabledDiskTemplates())]
475 if utils.IsLvmEnabled(qa_config.GetEnabledDiskTemplates()):
476 vgname = qa_config.get("vg-name", constants.DEFAULT_VG)
477 cmd.append("--vg-name=%s" % vgname)
479 AssertCommand(cmd, fail=False)
482 def _TestClusterModifyDiskTemplatesArguments(default_disk_template,
483 enabled_disk_templates):
484 """Tests argument handling of 'gnt-cluster modify' with respect to
485 the parameter '--enabled-disk-templates'. This test is independent
489 _RestoreEnabledDiskTemplates()
492 AssertCommand(["gnt-cluster", "modify",
493 "--enabled-disk-templates=pinkbunny"],
496 # duplicate entries do no harm
498 ["gnt-cluster", "modify",
499 "--enabled-disk-templates=%s,%s" %
500 (default_disk_template, default_disk_template)],
503 if constants.DT_DRBD8 in enabled_disk_templates:
504 # interaction with --drbd-usermode-helper option
505 drbd_usermode_helper = qa_config.get("drbd-usermode-helper", None)
506 if not drbd_usermode_helper:
507 drbd_usermode_helper = "/bin/true"
508 # specifying a helper when drbd gets disabled is ok. Note that drbd still
509 # has to be installed on the nodes in this case
510 AssertCommand(["gnt-cluster", "modify",
511 "--drbd-usermode-helper=%s" % drbd_usermode_helper,
512 "--enabled-disk-templates=%s" % constants.DT_DISKLESS],
514 # specifying a helper when drbd is re-enabled
515 AssertCommand(["gnt-cluster", "modify",
516 "--drbd-usermode-helper=%s" % drbd_usermode_helper,
517 "--enabled-disk-templates=%s" %
518 ",".join(enabled_disk_templates)],
522 def _TestClusterModifyDiskTemplatesVgName(enabled_disk_templates):
523 """Tests argument handling of 'gnt-cluster modify' with respect to
524 the parameter '--enabled-disk-templates' and '--vg-name'. This test is
525 independent of instances.
528 if not utils.IsLvmEnabled(enabled_disk_templates):
529 # These tests only make sense if lvm is enabled for QA
532 # determine an LVM and a non-LVM disk template for the tests
533 non_lvm_templates = list(set(enabled_disk_templates)
534 - set(utils.GetLvmDiskTemplates()))
535 lvm_template = list(set(enabled_disk_templates)
536 .intersection(set(utils.GetLvmDiskTemplates())))[0]
537 non_lvm_template = None
538 if non_lvm_templates:
539 non_lvm_template = non_lvm_templates[0]
541 # If no non-lvm disk template is available for QA, choose 'diskless' and
543 non_lvm_template = constants.ST_DISKLESS
545 vgname = qa_config.get("vg-name", constants.DEFAULT_VG)
547 # Clean start: unset volume group name, disable lvm storage
549 ["gnt-cluster", "modify",
550 "--enabled-disk-templates=%s" % non_lvm_template,
554 # Try to enable lvm, when no volume group is given
556 ["gnt-cluster", "modify",
557 "--enabled-disk-templates=%s" % lvm_template],
560 # Set volume group, with lvm still disabled: just a warning
561 AssertCommand(["gnt-cluster", "modify", "--vg-name=%s" % vgname], fail=False)
563 # Try unsetting vg name and enabling lvm at the same time
565 ["gnt-cluster", "modify",
566 "--enabled-disk-templates=%s" % lvm_template,
570 # Enable lvm with vg name present
572 ["gnt-cluster", "modify",
573 "--enabled-disk-templates=%s" % lvm_template],
576 # Try unsetting vg name with lvm still enabled
577 AssertCommand(["gnt-cluster", "modify", "--vg-name="], fail=True)
579 # Disable lvm with vg name still set
581 ["gnt-cluster", "modify", "--enabled-disk-templates=%s" % non_lvm_template],
584 # Try unsetting vg name with lvm disabled
585 AssertCommand(["gnt-cluster", "modify", "--vg-name="], fail=False)
587 # Set vg name and enable lvm at the same time
589 ["gnt-cluster", "modify",
590 "--enabled-disk-templates=%s" % lvm_template,
591 "--vg-name=%s" % vgname],
594 # Unset vg name and disable lvm at the same time
596 ["gnt-cluster", "modify",
597 "--enabled-disk-templates=%s" % non_lvm_template,
601 _RestoreEnabledDiskTemplates()
604 def _TestClusterModifyUsedDiskTemplate(instance_template,
605 enabled_disk_templates):
606 """Tests that disk templates that are currently in use by instances cannot
607 be disabled on the cluster.
610 # If the list of enabled disk templates contains only one template
611 # we need to add some other templates, because the list of enabled disk
612 # templates can only be set to a non-empty list.
613 new_disk_templates = list(set(enabled_disk_templates)
614 - set([instance_template]))
615 if not new_disk_templates:
616 new_disk_templates = list(set([constants.DT_DISKLESS, constants.DT_BLOCK])
617 - set([instance_template]))
619 ["gnt-cluster", "modify",
620 "--enabled-disk-templates=%s" %
621 ",".join(new_disk_templates)],
625 def _TestClusterModifyUnusedDiskTemplate(instance_template):
626 """Tests that unused disk templates can be disabled safely."""
627 all_disk_templates = constants.DISK_TEMPLATES
628 if not utils.IsLvmEnabled(qa_config.GetEnabledDiskTemplates()):
629 all_disk_templates = list(set(all_disk_templates) -
630 set(utils.GetLvmDiskTemplates()))
633 ["gnt-cluster", "modify",
634 "--enabled-disk-templates=%s" %
635 ",".join(all_disk_templates)],
637 new_disk_templates = [instance_template]
639 ["gnt-cluster", "modify",
640 "--enabled-disk-templates=%s" %
641 ",".join(new_disk_templates)],
645 def TestClusterModifyBe():
646 """gnt-cluster modify -B"""
649 (False, ["gnt-cluster", "modify", "-B", "maxmem=256"]),
650 (False, ["sh", "-c", "gnt-cluster info|grep '^ *maxmem: 256$'"]),
651 (False, ["gnt-cluster", "modify", "-B", "minmem=256"]),
652 (False, ["sh", "-c", "gnt-cluster info|grep '^ *minmem: 256$'"]),
653 (True, ["gnt-cluster", "modify", "-B", "maxmem=a"]),
654 (False, ["sh", "-c", "gnt-cluster info|grep '^ *maxmem: 256$'"]),
655 (True, ["gnt-cluster", "modify", "-B", "minmem=a"]),
656 (False, ["sh", "-c", "gnt-cluster info|grep '^ *minmem: 256$'"]),
657 (False, ["gnt-cluster", "modify", "-B", "maxmem=128,minmem=128"]),
658 (False, ["sh", "-c", "gnt-cluster info|grep '^ *maxmem: 128$'"]),
659 (False, ["sh", "-c", "gnt-cluster info|grep '^ *minmem: 128$'"]),
661 (False, ["gnt-cluster", "modify", "-B", "vcpus=4"]),
662 (False, ["sh", "-c", "gnt-cluster info|grep '^ *vcpus: 4$'"]),
663 (True, ["gnt-cluster", "modify", "-B", "vcpus=a"]),
664 (False, ["gnt-cluster", "modify", "-B", "vcpus=1"]),
665 (False, ["sh", "-c", "gnt-cluster info|grep '^ *vcpus: 1$'"]),
667 (False, ["gnt-cluster", "modify", "-B", "auto_balance=False"]),
668 (False, ["sh", "-c", "gnt-cluster info|grep '^ *auto_balance: False$'"]),
669 (True, ["gnt-cluster", "modify", "-B", "auto_balance=1"]),
670 (False, ["gnt-cluster", "modify", "-B", "auto_balance=True"]),
671 (False, ["sh", "-c", "gnt-cluster info|grep '^ *auto_balance: True$'"]),
673 AssertCommand(cmd, fail=fail)
675 # redo the original-requested BE parameters, if any
676 bep = qa_config.get("backend-parameters", "")
678 AssertCommand(["gnt-cluster", "modify", "-B", bep])
681 def _GetClusterIPolicy():
682 """Return the run-time values of the cluster-level instance policy.
685 @return: (policy, specs), where:
686 - policy is a dictionary of the policy values, instance specs excluded
687 - specs is a dictionary containing only the specs, using the internal
688 format (see L{constants.IPOLICY_DEFAULTS} for an example)
691 info = qa_utils.GetObjectInfo(["gnt-cluster", "info"])
692 policy = info["Instance policy - limits for instances"]
693 (ret_policy, ret_specs) = qa_utils.ParseIPolicy(policy)
696 assert "minmax" in ret_specs and "std" in ret_specs
697 assert len(ret_specs["minmax"]) > 0
698 assert len(ret_policy) > 0
699 return (ret_policy, ret_specs)
702 def TestClusterModifyIPolicy():
703 """gnt-cluster modify --ipolicy-*"""
704 basecmd = ["gnt-cluster", "modify"]
705 (old_policy, old_specs) = _GetClusterIPolicy()
706 for par in ["vcpu-ratio", "spindle-ratio"]:
707 curr_val = float(old_policy[par])
713 # Restore the old value
716 for (good, val) in test_values:
717 cmd = basecmd + ["--ipolicy-%s=%s" % (par, val)]
718 AssertCommand(cmd, fail=not good)
721 # Check the affected parameter
722 (eff_policy, eff_specs) = _GetClusterIPolicy()
723 AssertEqual(float(eff_policy[par]), curr_val)
724 # Check everything else
725 AssertEqual(eff_specs, old_specs)
726 for p in eff_policy.keys():
729 AssertEqual(eff_policy[p], old_policy[p])
731 # Disk templates are treated slightly differently
732 par = "disk-templates"
733 disp_str = "allowed disk templates"
734 curr_val = old_policy[disp_str]
736 (True, constants.DT_PLAIN),
737 (True, "%s,%s" % (constants.DT_PLAIN, constants.DT_DRBD8)),
738 (False, "thisisnotadisktemplate"),
740 # Restore the old value
741 (True, curr_val.replace(" ", "")),
743 for (good, val) in test_values:
744 cmd = basecmd + ["--ipolicy-%s=%s" % (par, val)]
745 AssertCommand(cmd, fail=not good)
748 # Check the affected parameter
749 (eff_policy, eff_specs) = _GetClusterIPolicy()
750 AssertEqual(eff_policy[disp_str].replace(" ", ""), curr_val)
751 # Check everything else
752 AssertEqual(eff_specs, old_specs)
753 for p in eff_policy.keys():
756 AssertEqual(eff_policy[p], old_policy[p])
759 def TestClusterSetISpecs(new_specs=None, diff_specs=None, fail=False,
761 """Change instance specs.
763 At most one of new_specs or diff_specs can be specified.
765 @type new_specs: dict
766 @param new_specs: new complete specs, in the same format returned by
767 L{_GetClusterIPolicy}
768 @type diff_specs: dict
769 @param diff_specs: partial specs, it can be an incomplete specifications, but
770 if min/max specs are specified, their number must match the number of the
773 @param fail: if the change is expected to fail
774 @type old_values: tuple
775 @param old_values: (old_policy, old_specs), as returned by
776 L{_GetClusterIPolicy}
777 @return: same as L{_GetClusterIPolicy}
780 build_cmd = lambda opts: ["gnt-cluster", "modify"] + opts
781 return qa_utils.TestSetISpecs(
782 new_specs=new_specs, diff_specs=diff_specs,
783 get_policy_fn=_GetClusterIPolicy, build_cmd_fn=build_cmd,
784 fail=fail, old_values=old_values)
787 def TestClusterModifyISpecs():
788 """gnt-cluster modify --specs-*"""
789 params = ["memory-size", "disk-size", "disk-count", "cpu-count", "nic-count"]
790 (cur_policy, cur_specs) = _GetClusterIPolicy()
791 # This test assumes that there is only one min/max bound
792 assert len(cur_specs[constants.ISPECS_MINMAX]) == 1
807 # This is to restore the old values
809 cur_specs[constants.ISPECS_MINMAX][0][constants.ISPECS_MIN][par],
810 cur_specs[constants.ISPECS_STD][par],
811 cur_specs[constants.ISPECS_MINMAX][0][constants.ISPECS_MAX][par])
813 for (good, mn, st, mx) in test_values:
815 constants.ISPECS_MINMAX: [{
816 constants.ISPECS_MIN: {par: mn},
817 constants.ISPECS_MAX: {par: mx}
819 constants.ISPECS_STD: {par: st}
821 cur_state = (cur_policy, cur_specs)
822 # We update cur_specs, as we've copied the values to restore already
823 (cur_policy, cur_specs) = TestClusterSetISpecs(
824 diff_specs=new_vals, fail=not good, old_values=cur_state)
826 # Get the ipolicy command
827 mnode = qa_config.GetMasterNode()
828 initcmd = GetCommandOutput(mnode.primary, "gnt-cluster show-ispecs-cmd")
829 modcmd = ["gnt-cluster", "modify"]
830 opts = initcmd.split()
831 assert opts[0:2] == ["gnt-cluster", "init"]
832 for k in range(2, len(opts) - 1):
833 if opts[k].startswith("--ipolicy-"):
834 assert k + 2 <= len(opts)
835 modcmd.extend(opts[k:k + 2])
836 # Re-apply the ipolicy (this should be a no-op)
837 AssertCommand(modcmd)
838 new_initcmd = GetCommandOutput(mnode.primary, "gnt-cluster show-ispecs-cmd")
839 AssertEqual(initcmd, new_initcmd)
842 def TestClusterInfo():
843 """gnt-cluster info"""
844 AssertCommand(["gnt-cluster", "info"])
847 def TestClusterRedistConf():
848 """gnt-cluster redist-conf"""
849 AssertCommand(["gnt-cluster", "redist-conf"])
852 def TestClusterGetmaster():
853 """gnt-cluster getmaster"""
854 AssertCommand(["gnt-cluster", "getmaster"])
857 def TestClusterVersion():
858 """gnt-cluster version"""
859 AssertCommand(["gnt-cluster", "version"])
862 def TestClusterRenewCrypto():
863 """gnt-cluster renew-crypto"""
864 master = qa_config.GetMasterNode()
866 # Conflicting options
867 cmd = ["gnt-cluster", "renew-crypto", "--force",
868 "--new-cluster-certificate", "--new-confd-hmac-key"]
870 ["--new-rapi-certificate", "--rapi-certificate=/dev/null"],
871 ["--new-cluster-domain-secret", "--cluster-domain-secret=/dev/null"],
873 for i in conflicting:
874 AssertCommand(cmd + i, fail=True)
876 # Invalid RAPI certificate
877 cmd = ["gnt-cluster", "renew-crypto", "--force",
878 "--rapi-certificate=/dev/null"]
879 AssertCommand(cmd, fail=True)
881 rapi_cert_backup = qa_utils.BackupFile(master.primary,
882 pathutils.RAPI_CERT_FILE)
884 # Custom RAPI certificate
885 fh = tempfile.NamedTemporaryFile()
887 # Ensure certificate doesn't cause "gnt-cluster verify" to complain
888 validity = constants.SSL_CERT_EXPIRATION_WARN * 3
890 utils.GenerateSelfSignedSslCert(fh.name, validity=validity)
892 tmpcert = qa_utils.UploadFile(master.primary, fh.name)
894 AssertCommand(["gnt-cluster", "renew-crypto", "--force",
895 "--rapi-certificate=%s" % tmpcert])
897 AssertCommand(["rm", "-f", tmpcert])
899 # Custom cluster domain secret
900 cds_fh = tempfile.NamedTemporaryFile()
901 cds_fh.write(utils.GenerateSecret())
905 tmpcds = qa_utils.UploadFile(master.primary, cds_fh.name)
907 AssertCommand(["gnt-cluster", "renew-crypto", "--force",
908 "--cluster-domain-secret=%s" % tmpcds])
910 AssertCommand(["rm", "-f", tmpcds])
913 AssertCommand(["gnt-cluster", "renew-crypto", "--force",
914 "--new-cluster-certificate", "--new-confd-hmac-key",
915 "--new-rapi-certificate", "--new-cluster-domain-secret"])
917 # Restore RAPI certificate
918 AssertCommand(["gnt-cluster", "renew-crypto", "--force",
919 "--rapi-certificate=%s" % rapi_cert_backup])
921 AssertCommand(["rm", "-f", rapi_cert_backup])
924 def TestClusterBurnin():
926 master = qa_config.GetMasterNode()
928 options = qa_config.get("options", {})
929 disk_template = options.get("burnin-disk-template", constants.DT_DRBD8)
930 parallel = options.get("burnin-in-parallel", False)
931 check_inst = options.get("burnin-check-instances", False)
932 do_rename = options.get("burnin-rename", "")
933 do_reboot = options.get("burnin-reboot", True)
934 reboot_types = options.get("reboot-types", constants.REBOOT_TYPES)
936 # Get as many instances as we need
940 num = qa_config.get("options", {}).get("burnin-instances", 1)
941 for _ in range(0, num):
942 instances.append(qa_config.AcquireInstance())
943 except qa_error.OutOfInstancesError:
944 print "Not enough instances, continuing anyway."
946 if len(instances) < 1:
947 raise qa_error.Error("Burnin needs at least one instance")
949 script = qa_utils.UploadFile(master.primary, "../tools/burnin")
951 disks = qa_config.GetDiskOptions()
954 "--os=%s" % qa_config.get("os"),
955 "--minmem-size=%s" % qa_config.get(constants.BE_MINMEM),
956 "--maxmem-size=%s" % qa_config.get(constants.BE_MAXMEM),
957 "--disk-size=%s" % ",".join([d.get("size") for d in disks]),
958 "--disk-growth=%s" % ",".join([d.get("growth") for d in disks]),
959 "--disk-template=%s" % disk_template]
961 cmd.append("--parallel")
962 cmd.append("--early-release")
964 cmd.append("--http-check")
966 cmd.append("--rename=%s" % do_rename)
968 cmd.append("--no-reboot")
970 cmd.append("--reboot-types=%s" % ",".join(reboot_types))
971 cmd += [inst.name for inst in instances]
974 AssertCommand(["rm", "-f", script])
977 for inst in instances:
981 def TestClusterMasterFailover():
982 """gnt-cluster master-failover"""
983 master = qa_config.GetMasterNode()
984 failovermaster = qa_config.AcquireNode(exclude=master)
986 cmd = ["gnt-cluster", "master-failover"]
988 AssertCommand(cmd, node=failovermaster)
989 # Back to original master node
990 AssertCommand(cmd, node=master)
992 failovermaster.Release()
995 def _NodeQueueDrainFile(node):
996 """Returns path to queue drain file for a node.
999 return qa_utils.MakeNodePath(node, pathutils.JOB_QUEUE_DRAIN_FILE)
1002 def _AssertDrainFile(node, **kwargs):
1003 """Checks for the queue drain file.
1006 AssertCommand(["test", "-f", _NodeQueueDrainFile(node)], node=node, **kwargs)
1009 def TestClusterMasterFailoverWithDrainedQueue():
1010 """gnt-cluster master-failover with drained queue"""
1011 master = qa_config.GetMasterNode()
1012 failovermaster = qa_config.AcquireNode(exclude=master)
1014 # Ensure queue is not drained
1015 for node in [master, failovermaster]:
1016 _AssertDrainFile(node, fail=True)
1018 # Drain queue on failover master
1019 AssertCommand(["touch", _NodeQueueDrainFile(failovermaster)],
1020 node=failovermaster)
1022 cmd = ["gnt-cluster", "master-failover"]
1024 _AssertDrainFile(failovermaster)
1025 AssertCommand(cmd, node=failovermaster)
1026 _AssertDrainFile(master, fail=True)
1027 _AssertDrainFile(failovermaster, fail=True)
1029 # Back to original master node
1030 AssertCommand(cmd, node=master)
1032 failovermaster.Release()
1034 # Ensure queue is not drained
1035 for node in [master, failovermaster]:
1036 _AssertDrainFile(node, fail=True)
1039 def TestClusterCopyfile():
1040 """gnt-cluster copyfile"""
1041 master = qa_config.GetMasterNode()
1043 uniqueid = utils.NewUUID()
1045 # Create temporary file
1046 f = tempfile.NamedTemporaryFile()
1051 # Upload file to master node
1052 testname = qa_utils.UploadFile(master.primary, f.name)
1054 # Copy file to all nodes
1055 AssertCommand(["gnt-cluster", "copyfile", testname])
1056 _CheckFileOnAllNodes(testname, uniqueid)
1058 _RemoveFileFromAllNodes(testname)
1061 def TestClusterCommand():
1062 """gnt-cluster command"""
1063 uniqueid = utils.NewUUID()
1064 rfile = "/tmp/gnt%s" % utils.NewUUID()
1065 rcmd = utils.ShellQuoteArgs(["echo", "-n", uniqueid])
1066 cmd = utils.ShellQuoteArgs(["gnt-cluster", "command",
1067 "%s >%s" % (rcmd, rfile)])
1071 _CheckFileOnAllNodes(rfile, uniqueid)
1073 _RemoveFileFromAllNodes(rfile)
1076 def TestClusterDestroy():
1077 """gnt-cluster destroy"""
1078 AssertCommand(["gnt-cluster", "destroy", "--yes-do-it"])
1081 def TestClusterRepairDiskSizes():
1082 """gnt-cluster repair-disk-sizes"""
1083 AssertCommand(["gnt-cluster", "repair-disk-sizes"])
1086 def TestSetExclStorCluster(newvalue):
1087 """Set the exclusive_storage node parameter at the cluster level.
1089 @type newvalue: bool
1090 @param newvalue: New value of exclusive_storage
1092 @return: The old value of exclusive_storage
1095 es_path = ["Default node parameters", "exclusive_storage"]
1096 oldvalue = _GetClusterField(es_path)
1097 AssertCommand(["gnt-cluster", "modify", "--node-parameters",
1098 "exclusive_storage=%s" % newvalue])
1099 effvalue = _GetClusterField(es_path)
1100 if effvalue != newvalue:
1101 raise qa_error.Error("exclusive_storage has the wrong value: %s instead"
1102 " of %s" % (effvalue, newvalue))
1103 qa_config.SetExclusiveStorage(newvalue)
1107 def TestExclStorSharedPv(node):
1108 """cluster-verify reports LVs that share the same PV with exclusive_storage.
1111 vgname = qa_config.get("vg-name", constants.DEFAULT_VG)
1112 lvname1 = _QA_LV_PREFIX + "vol1"
1113 lvname2 = _QA_LV_PREFIX + "vol2"
1114 node_name = node.primary
1115 AssertCommand(["lvcreate", "-L1G", "-n", lvname1, vgname], node=node_name)
1116 AssertClusterVerify(fail=True, errors=[constants.CV_ENODEORPHANLV])
1117 AssertCommand(["lvcreate", "-L1G", "-n", lvname2, vgname], node=node_name)
1118 AssertClusterVerify(fail=True, errors=[constants.CV_ENODELVM,
1119 constants.CV_ENODEORPHANLV])
1120 AssertCommand(["lvremove", "-f", "/".join([vgname, lvname1])], node=node_name)
1121 AssertCommand(["lvremove", "-f", "/".join([vgname, lvname2])], node=node_name)
1122 AssertClusterVerify()