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("default-file-storage-dir",
189 pathutils.DEFAULT_FILE_STORAGE_DIR))
191 for spec_type in ("mem-size", "disk-size", "disk-count", "cpu-count",
193 for spec_val in ("min", "max", "std"):
194 spec = qa_config.get("ispec_%s_%s" %
195 (spec_type.replace("-", "_"), spec_val), None)
197 cmd.append("--specs-%s=%s=%d" % (spec_type, spec_val, spec))
200 cmd.append("--secondary-ip=%s" % master.secondary)
202 if utils.IsLvmEnabled(qa_config.GetEnabledDiskTemplates()):
203 vgname = qa_config.get("vg-name", constants.DEFAULT_VG)
205 cmd.append("--vg-name=%s" % vgname)
207 raise qa_error.Error("Please specify a volume group if you enable"
208 " lvm-based disk templates in the QA.")
210 master_netdev = qa_config.get("master-netdev", None)
212 cmd.append("--master-netdev=%s" % master_netdev)
214 nicparams = qa_config.get("default-nicparams", None)
216 cmd.append("--nic-parameters=%s" %
217 ",".join(utils.FormatKeyValue(nicparams)))
219 # Cluster value of the exclusive-storage node parameter
220 e_s = qa_config.get("exclusive-storage")
222 cmd.extend(["--node-parameters", "exclusive_storage=%s" % e_s])
225 qa_config.SetExclusiveStorage(e_s)
227 extra_args = qa_config.get("cluster-init-args")
230 # This option was removed in 2.10, but in order to not break QA of older
231 # branches we remove it from the extra_args if it is in there.
232 opt_drbd_storage = "--no-drbd-storage"
233 if opt_drbd_storage in extra_args:
234 extra_args.remove(opt_drbd_storage)
235 cmd.extend(extra_args)
237 cmd.append(qa_config.get("name"))
241 cmd = ["gnt-cluster", "modify"]
243 # hypervisor parameter modifications
244 hvp = qa_config.get("hypervisor-parameters", {})
245 for k, v in hvp.items():
246 cmd.extend(["-H", "%s:%s" % (k, v)])
247 # backend parameter modifications
248 bep = qa_config.get("backend-parameters", "")
250 cmd.extend(["-B", bep])
256 osp = qa_config.get("os-parameters", {})
257 for k, v in osp.items():
258 AssertCommand(["gnt-os", "modify", "-O", v, k])
260 # OS hypervisor parameters
261 os_hvp = qa_config.get("os-hvp", {})
262 for os_name in os_hvp:
263 for hv, hvp in os_hvp[os_name].items():
264 AssertCommand(["gnt-os", "modify", "-H", "%s:%s" % (hv, hvp), os_name])
267 def TestClusterRename():
268 """gnt-cluster rename"""
269 cmd = ["gnt-cluster", "rename", "-f"]
271 original_name = qa_config.get("name")
272 rename_target = qa_config.get("rename", None)
273 if rename_target is None:
274 print qa_utils.FormatError('"rename" entry is missing')
278 cmd + [rename_target],
280 cmd + [original_name],
286 def TestClusterOob():
287 """out-of-band framework"""
288 oob_path_exists = "/tmp/ganeti-qa-oob-does-exist-%s" % utils.NewUUID()
290 AssertCommand(_CLUSTER_VERIFY)
291 AssertCommand(["gnt-cluster", "modify", "--node-parameters",
292 "oob_program=/tmp/ganeti-qa-oob-does-not-exist-%s" %
295 AssertCommand(_CLUSTER_VERIFY, fail=True)
297 AssertCommand(["touch", oob_path_exists])
298 AssertCommand(["chmod", "0400", oob_path_exists])
299 AssertCommand(["gnt-cluster", "copyfile", oob_path_exists])
302 AssertCommand(["gnt-cluster", "modify", "--node-parameters",
303 "oob_program=%s" % oob_path_exists])
305 AssertCommand(_CLUSTER_VERIFY, fail=True)
307 AssertCommand(["chmod", "0500", oob_path_exists])
308 AssertCommand(["gnt-cluster", "copyfile", oob_path_exists])
310 AssertCommand(_CLUSTER_VERIFY)
312 AssertCommand(["gnt-cluster", "command", "rm", oob_path_exists])
314 AssertCommand(["gnt-cluster", "modify", "--node-parameters",
318 def TestClusterEpo():
319 """gnt-cluster epo"""
320 master = qa_config.GetMasterNode()
322 # Assert that OOB is unavailable for all nodes
323 result_output = GetCommandOutput(master.primary,
324 "gnt-node list --verbose --no-headers -o"
326 AssertEqual(compat.all(powered == "(unavail)"
327 for powered in result_output.splitlines()), True)
330 AssertCommand(["gnt-cluster", "epo", "--groups", "--all"], fail=True)
331 # --all doesn't expect arguments
332 AssertCommand(["gnt-cluster", "epo", "--all", "some_arg"], fail=True)
334 # Unless --all is given master is not allowed to be in the list
335 AssertCommand(["gnt-cluster", "epo", "-f", master.primary], fail=True)
337 # This shouldn't fail
338 AssertCommand(["gnt-cluster", "epo", "-f", "--all"])
340 # All instances should have been stopped now
341 result_output = GetCommandOutput(master.primary,
342 "gnt-instance list --no-headers -o status")
343 # ERROR_down because the instance is stopped but not recorded as such
344 AssertEqual(compat.all(status == "ERROR_down"
345 for status in result_output.splitlines()), True)
347 # Now start everything again
348 AssertCommand(["gnt-cluster", "epo", "--on", "-f", "--all"])
350 # All instances should have been started now
351 result_output = GetCommandOutput(master.primary,
352 "gnt-instance list --no-headers -o status")
353 AssertEqual(compat.all(status == "running"
354 for status in result_output.splitlines()), True)
357 def TestClusterVerify():
358 """gnt-cluster verify"""
359 AssertCommand(_CLUSTER_VERIFY)
360 AssertCommand(["gnt-cluster", "verify-disks"])
363 def TestClusterVerifyDisksBrokenDRBD(instance, inst_nodes):
364 """gnt-cluster verify-disks with broken DRBD"""
365 qa_daemon.TestPauseWatcher()
368 info = qa_instance.GetInstanceInfo(instance.name)
369 snode = inst_nodes[1]
370 for idx, minor in enumerate(info["drbd-minors"][snode.primary]):
373 "(drbdsetup %d down >/dev/null 2>&1;" \
374 " drbdsetup down resource%d >/dev/null 2>&1) || /bin/true" % \
378 "(drbdsetup %d detach >/dev/null 2>&1;" \
379 " drbdsetup detach %d >/dev/null 2>&1) || /bin/true" % \
381 AssertCommand(break_drbd_cmd, node=snode)
383 verify_output = GetCommandOutput(qa_config.GetMasterNode().primary,
384 "gnt-cluster verify-disks")
385 activation_msg = "Activating disks for instance '%s'" % instance.name
386 if activation_msg not in verify_output:
387 raise qa_error.Error("gnt-cluster verify-disks did not activate broken"
388 " DRBD disks:\n%s" % verify_output)
390 verify_output = GetCommandOutput(qa_config.GetMasterNode().primary,
391 "gnt-cluster verify-disks")
392 if activation_msg in verify_output:
393 raise qa_error.Error("gnt-cluster verify-disks wants to activate broken"
394 " DRBD disks on second attempt:\n%s" % verify_output)
396 AssertCommand(_CLUSTER_VERIFY)
398 qa_daemon.TestResumeWatcher()
402 """gnt-debug test-jobqueue"""
403 AssertCommand(["gnt-debug", "test-jobqueue"])
407 """gnt-debug delay"""
408 AssertCommand(["gnt-debug", "delay", "1"])
409 AssertCommand(["gnt-debug", "delay", "--no-master", "1"])
410 AssertCommand(["gnt-debug", "delay", "--no-master",
411 "-n", node.primary, "1"])
414 def TestClusterReservedLvs():
415 """gnt-cluster reserved lvs"""
416 # if no lvm-based templates are supported, skip the test
417 if not qa_config.IsStorageTypeSupported(constants.ST_LVM_VG):
419 vgname = qa_config.get("vg-name", constants.DEFAULT_VG)
420 lvname = _QA_LV_PREFIX + "test"
421 lvfullname = "/".join([vgname, lvname])
423 (False, _CLUSTER_VERIFY),
424 (False, ["gnt-cluster", "modify", "--reserved-lvs", ""]),
425 (False, ["lvcreate", "-L1G", "-n", lvname, vgname]),
426 (True, _CLUSTER_VERIFY),
427 (False, ["gnt-cluster", "modify", "--reserved-lvs",
428 "%s,.*/other-test" % lvfullname]),
429 (False, _CLUSTER_VERIFY),
430 (False, ["gnt-cluster", "modify", "--reserved-lvs",
431 ".*/%s.*" % _QA_LV_PREFIX]),
432 (False, _CLUSTER_VERIFY),
433 (False, ["gnt-cluster", "modify", "--reserved-lvs", ""]),
434 (True, _CLUSTER_VERIFY),
435 (False, ["lvremove", "-f", lvfullname]),
436 (False, _CLUSTER_VERIFY),
438 AssertCommand(cmd, fail=fail)
441 def TestClusterModifyEmpty():
442 """gnt-cluster modify"""
443 AssertCommand(["gnt-cluster", "modify"], fail=True)
446 def TestClusterModifyDisk():
447 """gnt-cluster modify -D"""
448 for param in _FAIL_PARAMS:
449 AssertCommand(["gnt-cluster", "modify", "-D", param], fail=True)
452 def _GetOtherEnabledDiskTemplate(undesired_disk_templates,
453 enabled_disk_templates):
454 """Returns one template that is not in the undesired set.
456 @type undesired_disk_templates: list of string
457 @param undesired_disk_templates: a list of disk templates that we want to
458 exclude when drawing one disk template from the list of enabled
460 @type enabled_disk_templates: list of string
461 @param enabled_disk_templates: list of enabled disk templates (in QA)
464 desired_templates = list(set(enabled_disk_templates)
465 - set(undesired_disk_templates))
466 if desired_templates:
467 template = desired_templates[0]
469 # If no desired disk template is available for QA, choose 'diskless' and
471 template = constants.ST_DISKLESS
476 def TestClusterModifyFileBasedStorageDir(
477 file_disk_template, dir_config_key, default_dir, option_name):
478 """Tests gnt-cluster modify wrt to file-based directory options.
480 @type file_disk_template: string
481 @param file_disk_template: file-based disk template
482 @type dir_config_key: string
483 @param dir_config_key: key for the QA config to retrieve the default
485 @type default_dir: string
486 @param default_dir: default directory, if the QA config does not specify
488 @type option_name: string
489 @param option_name: name of the option of 'gnt-cluster modify' to
493 enabled_disk_templates = qa_config.GetEnabledDiskTemplates()
494 assert file_disk_template in [constants.DT_FILE, constants.DT_SHARED_FILE]
495 if not qa_config.IsTemplateSupported(file_disk_template):
498 # Get some non-file-based disk template to disable file storage
499 other_disk_template = _GetOtherEnabledDiskTemplate(
500 utils.storage.GetDiskTemplatesOfStorageType(constants.ST_FILE),
501 enabled_disk_templates)
503 file_storage_dir = qa_config.get(dir_config_key, default_dir)
504 invalid_file_storage_dir = "/boot/"
507 (False, ["gnt-cluster", "modify",
508 "--enabled-disk-templates=%s" % file_disk_template,
509 "--ipolicy-disk-templates=%s" % file_disk_template]),
510 (False, ["gnt-cluster", "modify",
511 "--%s=%s" % (option_name, file_storage_dir)]),
512 (False, ["gnt-cluster", "modify",
513 "--%s=%s" % (option_name, invalid_file_storage_dir)]),
514 # file storage dir is set to an inacceptable path, thus verify
516 (True, ["gnt-cluster", "verify"]),
517 # unsetting the storage dir while file storage is enabled
519 (True, ["gnt-cluster", "modify",
520 "--%s=" % option_name]),
521 (False, ["gnt-cluster", "modify",
522 "--%s=%s" % (option_name, file_storage_dir)]),
523 (False, ["gnt-cluster", "modify",
524 "--enabled-disk-templates=%s" % other_disk_template,
525 "--ipolicy-disk-templates=%s" % other_disk_template]),
526 (False, ["gnt-cluster", "modify",
527 "--%s=%s" % (option_name, invalid_file_storage_dir)]),
528 # file storage is set to an inacceptable path, but file storage
529 # is disabled, thus verify should not fail
530 (False, ["gnt-cluster", "verify"]),
531 # unsetting the file storage dir while file storage is not enabled
533 (False, ["gnt-cluster", "modify",
534 "--%s=" % option_name]),
535 # resetting everything to sane values
536 (False, ["gnt-cluster", "modify",
537 "--%s=%s" % (option_name, file_storage_dir),
538 "--enabled-disk-templates=%s" % ",".join(enabled_disk_templates),
539 "--ipolicy-disk-templates=%s" % ",".join(enabled_disk_templates)])
541 AssertCommand(cmd, fail=fail)
544 def TestClusterModifyFileStorageDir():
545 """gnt-cluster modify --file-storage-dir=..."""
546 TestClusterModifyFileBasedStorageDir(
547 constants.DT_FILE, "default-file-storage-dir",
548 pathutils.DEFAULT_FILE_STORAGE_DIR,
552 def TestClusterModifySharedFileStorageDir():
553 """gnt-cluster modify --shared-file-storage-dir=..."""
554 TestClusterModifyFileBasedStorageDir(
555 constants.DT_SHARED_FILE, "default-shared-file-storage-dir",
556 pathutils.DEFAULT_SHARED_FILE_STORAGE_DIR,
557 "shared-file-storage-dir")
560 def TestClusterModifyDiskTemplates():
561 """gnt-cluster modify --enabled-disk-templates=..."""
562 enabled_disk_templates = qa_config.GetEnabledDiskTemplates()
563 default_disk_template = qa_config.GetDefaultDiskTemplate()
565 _TestClusterModifyDiskTemplatesArguments(default_disk_template)
566 _TestClusterModifyDiskTemplatesDrbdHelper(enabled_disk_templates)
567 _TestClusterModifyDiskTemplatesVgName(enabled_disk_templates)
569 _RestoreEnabledDiskTemplates()
570 nodes = qa_config.AcquireManyNodes(2)
572 instance_template = enabled_disk_templates[0]
573 instance = qa_instance.CreateInstanceByDiskTemplate(nodes, instance_template)
575 _TestClusterModifyUnusedDiskTemplate(instance_template)
576 _TestClusterModifyUsedDiskTemplate(instance_template,
577 enabled_disk_templates)
579 qa_instance.TestInstanceRemove(instance)
580 _RestoreEnabledDiskTemplates()
583 def _RestoreEnabledDiskTemplates():
584 """Sets the list of enabled disk templates back to the list of enabled disk
585 templates from the QA configuration. This can be used to make sure that
586 the tests that modify the list of disk templates do not interfere with
590 enabled_disk_templates = qa_config.GetEnabledDiskTemplates()
591 cmd = ["gnt-cluster", "modify",
592 "--enabled-disk-templates=%s" % ",".join(enabled_disk_templates),
593 "--ipolicy-disk-templates=%s" % ",".join(enabled_disk_templates),
596 if utils.IsLvmEnabled(qa_config.GetEnabledDiskTemplates()):
597 vgname = qa_config.get("vg-name", constants.DEFAULT_VG)
598 cmd.append("--vg-name=%s" % vgname)
600 AssertCommand(cmd, fail=False)
603 def _TestClusterModifyDiskTemplatesDrbdHelper(enabled_disk_templates):
604 """Tests argument handling of 'gnt-cluster modify' with respect to
605 the parameter '--drbd-usermode-helper'. This test is independent
609 _RestoreEnabledDiskTemplates()
611 if constants.DT_DRBD8 not in enabled_disk_templates:
613 if constants.DT_PLAIN not in enabled_disk_templates:
616 drbd_usermode_helper = qa_config.get("drbd-usermode-helper", "/bin/true")
617 bogus_usermode_helper = "/tmp/pinkbunny"
618 for command, fail in \
619 [(["gnt-cluster", "modify",
620 "--enabled-disk-templates=%s" % constants.DT_DRBD8,
621 "--ipolicy-disk-templates=%s" % constants.DT_DRBD8], False),
622 (["gnt-cluster", "modify",
623 "--drbd-usermode-helper=%s" % drbd_usermode_helper], False),
624 (["gnt-cluster", "modify",
625 "--drbd-usermode-helper=%s" % bogus_usermode_helper], True),
626 # unsetting helper when DRBD is enabled should not work
627 (["gnt-cluster", "modify",
628 "--drbd-usermode-helper="], True),
629 (["gnt-cluster", "modify",
630 "--enabled-disk-templates=%s" % constants.DT_PLAIN,
631 "--ipolicy-disk-templates=%s" % constants.DT_PLAIN], False),
632 (["gnt-cluster", "modify",
633 "--drbd-usermode-helper="], False),
634 (["gnt-cluster", "modify",
635 "--drbd-usermode-helper=%s" % drbd_usermode_helper], False),
636 (["gnt-cluster", "modify",
637 "--drbd-usermode-helper=%s" % drbd_usermode_helper,
638 "--enabled-disk-templates=%s" % constants.DT_DRBD8,
639 "--ipolicy-disk-templates=%s" % constants.DT_DRBD8], False),
640 (["gnt-cluster", "modify",
641 "--drbd-usermode-helper=",
642 "--enabled-disk-templates=%s" % constants.DT_PLAIN,
643 "--ipolicy-disk-templates=%s" % constants.DT_PLAIN], False),
644 (["gnt-cluster", "modify",
645 "--drbd-usermode-helper=%s" % drbd_usermode_helper,
646 "--enabled-disk-templates=%s" % constants.DT_DRBD8,
647 "--ipolicy-disk-templates=%s" % constants.DT_DRBD8], False),
649 AssertCommand(command, fail=fail)
650 _RestoreEnabledDiskTemplates()
653 def _TestClusterModifyDiskTemplatesArguments(default_disk_template):
654 """Tests argument handling of 'gnt-cluster modify' with respect to
655 the parameter '--enabled-disk-templates'. This test is independent
659 _RestoreEnabledDiskTemplates()
662 AssertCommand(["gnt-cluster", "modify",
663 "--enabled-disk-templates=pinkbunny"],
666 # duplicate entries do no harm
668 ["gnt-cluster", "modify",
669 "--enabled-disk-templates=%s,%s" %
670 (default_disk_template, default_disk_template),
671 "--ipolicy-disk-templates=%s" % default_disk_template],
675 def _TestClusterModifyDiskTemplatesVgName(enabled_disk_templates):
676 """Tests argument handling of 'gnt-cluster modify' with respect to
677 the parameter '--enabled-disk-templates' and '--vg-name'. This test is
678 independent of instances.
681 if not utils.IsLvmEnabled(enabled_disk_templates):
682 # These tests only make sense if lvm is enabled for QA
685 # determine an LVM and a non-LVM disk template for the tests
686 non_lvm_template = _GetOtherEnabledDiskTemplate(utils.GetLvmDiskTemplates(),
687 enabled_disk_templates)
688 lvm_template = list(set(enabled_disk_templates)
689 .intersection(set(utils.GetLvmDiskTemplates())))[0]
691 vgname = qa_config.get("vg-name", constants.DEFAULT_VG)
693 # Clean start: unset volume group name, disable lvm storage
695 ["gnt-cluster", "modify",
696 "--enabled-disk-templates=%s" % non_lvm_template,
697 "--ipolicy-disk-templates=%s" % non_lvm_template,
701 # Try to enable lvm, when no volume group is given
703 ["gnt-cluster", "modify",
704 "--enabled-disk-templates=%s" % lvm_template,
705 "--ipolicy-disk-templates=%s" % lvm_template],
708 # Set volume group, with lvm still disabled: just a warning
709 AssertCommand(["gnt-cluster", "modify", "--vg-name=%s" % vgname], fail=False)
711 # Try unsetting vg name and enabling lvm at the same time
713 ["gnt-cluster", "modify",
714 "--enabled-disk-templates=%s" % lvm_template,
715 "--ipolicy-disk-templates=%s" % lvm_template,
719 # Enable lvm with vg name present
721 ["gnt-cluster", "modify",
722 "--enabled-disk-templates=%s" % lvm_template,
723 "--ipolicy-disk-templates=%s" % lvm_template],
726 # Try unsetting vg name with lvm still enabled
727 AssertCommand(["gnt-cluster", "modify", "--vg-name="], fail=True)
729 # Disable lvm with vg name still set
731 ["gnt-cluster", "modify",
732 "--enabled-disk-templates=%s" % non_lvm_template,
733 "--ipolicy-disk-templates=%s" % non_lvm_template,
737 # Try unsetting vg name with lvm disabled
738 AssertCommand(["gnt-cluster", "modify", "--vg-name="], fail=False)
740 # Set vg name and enable lvm at the same time
742 ["gnt-cluster", "modify",
743 "--enabled-disk-templates=%s" % lvm_template,
744 "--ipolicy-disk-templates=%s" % lvm_template,
745 "--vg-name=%s" % vgname],
748 # Unset vg name and disable lvm at the same time
750 ["gnt-cluster", "modify",
751 "--enabled-disk-templates=%s" % non_lvm_template,
752 "--ipolicy-disk-templates=%s" % non_lvm_template,
756 _RestoreEnabledDiskTemplates()
759 def _TestClusterModifyUsedDiskTemplate(instance_template,
760 enabled_disk_templates):
761 """Tests that disk templates that are currently in use by instances cannot
762 be disabled on the cluster.
765 # If the list of enabled disk templates contains only one template
766 # we need to add some other templates, because the list of enabled disk
767 # templates can only be set to a non-empty list.
768 new_disk_templates = list(set(enabled_disk_templates)
769 - set([instance_template]))
770 if not new_disk_templates:
771 new_disk_templates = list(set([constants.DT_DISKLESS, constants.DT_BLOCK])
772 - set([instance_template]))
774 ["gnt-cluster", "modify",
775 "--enabled-disk-templates=%s" % ",".join(new_disk_templates),
776 "--ipolicy-disk-templates=%s" % ",".join(new_disk_templates)],
780 def _TestClusterModifyUnusedDiskTemplate(instance_template):
781 """Tests that unused disk templates can be disabled safely."""
782 all_disk_templates = constants.DISK_TEMPLATES
783 if not utils.IsLvmEnabled(qa_config.GetEnabledDiskTemplates()):
784 all_disk_templates = list(set(all_disk_templates) -
785 set(utils.GetLvmDiskTemplates()))
788 ["gnt-cluster", "modify",
789 "--enabled-disk-templates=%s" % ",".join(all_disk_templates),
790 "--ipolicy-disk-templates=%s" % ",".join(all_disk_templates)],
792 new_disk_templates = [instance_template]
794 ["gnt-cluster", "modify",
795 "--enabled-disk-templates=%s" % ",".join(new_disk_templates),
796 "--ipolicy-disk-templates=%s" % ",".join(new_disk_templates)],
800 def TestClusterModifyBe():
801 """gnt-cluster modify -B"""
804 (False, ["gnt-cluster", "modify", "-B", "maxmem=256"]),
805 (False, ["sh", "-c", "gnt-cluster info|grep '^ *maxmem: 256$'"]),
806 (False, ["gnt-cluster", "modify", "-B", "minmem=256"]),
807 (False, ["sh", "-c", "gnt-cluster info|grep '^ *minmem: 256$'"]),
808 (True, ["gnt-cluster", "modify", "-B", "maxmem=a"]),
809 (False, ["sh", "-c", "gnt-cluster info|grep '^ *maxmem: 256$'"]),
810 (True, ["gnt-cluster", "modify", "-B", "minmem=a"]),
811 (False, ["sh", "-c", "gnt-cluster info|grep '^ *minmem: 256$'"]),
812 (False, ["gnt-cluster", "modify", "-B", "maxmem=128,minmem=128"]),
813 (False, ["sh", "-c", "gnt-cluster info|grep '^ *maxmem: 128$'"]),
814 (False, ["sh", "-c", "gnt-cluster info|grep '^ *minmem: 128$'"]),
816 (False, ["gnt-cluster", "modify", "-B", "vcpus=4"]),
817 (False, ["sh", "-c", "gnt-cluster info|grep '^ *vcpus: 4$'"]),
818 (True, ["gnt-cluster", "modify", "-B", "vcpus=a"]),
819 (False, ["gnt-cluster", "modify", "-B", "vcpus=1"]),
820 (False, ["sh", "-c", "gnt-cluster info|grep '^ *vcpus: 1$'"]),
822 (False, ["gnt-cluster", "modify", "-B", "auto_balance=False"]),
823 (False, ["sh", "-c", "gnt-cluster info|grep '^ *auto_balance: False$'"]),
824 (True, ["gnt-cluster", "modify", "-B", "auto_balance=1"]),
825 (False, ["gnt-cluster", "modify", "-B", "auto_balance=True"]),
826 (False, ["sh", "-c", "gnt-cluster info|grep '^ *auto_balance: True$'"]),
828 AssertCommand(cmd, fail=fail)
830 # redo the original-requested BE parameters, if any
831 bep = qa_config.get("backend-parameters", "")
833 AssertCommand(["gnt-cluster", "modify", "-B", bep])
836 def _GetClusterIPolicy():
837 """Return the run-time values of the cluster-level instance policy.
840 @return: (policy, specs), where:
841 - policy is a dictionary of the policy values, instance specs excluded
842 - specs is a dictionary containing only the specs, using the internal
843 format (see L{constants.IPOLICY_DEFAULTS} for an example)
846 info = qa_utils.GetObjectInfo(["gnt-cluster", "info"])
847 policy = info["Instance policy - limits for instances"]
848 (ret_policy, ret_specs) = qa_utils.ParseIPolicy(policy)
851 assert "minmax" in ret_specs and "std" in ret_specs
852 assert len(ret_specs["minmax"]) > 0
853 assert len(ret_policy) > 0
854 return (ret_policy, ret_specs)
857 def TestClusterModifyIPolicy():
858 """gnt-cluster modify --ipolicy-*"""
859 basecmd = ["gnt-cluster", "modify"]
860 (old_policy, old_specs) = _GetClusterIPolicy()
861 for par in ["vcpu-ratio", "spindle-ratio"]:
862 curr_val = float(old_policy[par])
868 # Restore the old value
871 for (good, val) in test_values:
872 cmd = basecmd + ["--ipolicy-%s=%s" % (par, val)]
873 AssertCommand(cmd, fail=not good)
876 # Check the affected parameter
877 (eff_policy, eff_specs) = _GetClusterIPolicy()
878 AssertEqual(float(eff_policy[par]), curr_val)
879 # Check everything else
880 AssertEqual(eff_specs, old_specs)
881 for p in eff_policy.keys():
884 AssertEqual(eff_policy[p], old_policy[p])
886 # Allowing disk templates via ipolicy requires them to be
887 # enabled on the cluster.
888 if not (qa_config.IsTemplateSupported(constants.DT_PLAIN)
889 and qa_config.IsTemplateSupported(constants.DT_DRBD8)):
891 # Disk templates are treated slightly differently
892 par = "disk-templates"
893 disp_str = "allowed disk templates"
894 curr_val = old_policy[disp_str]
896 (True, constants.DT_PLAIN),
897 (True, "%s,%s" % (constants.DT_PLAIN, constants.DT_DRBD8)),
898 (False, "thisisnotadisktemplate"),
900 # Restore the old value
901 (True, curr_val.replace(" ", "")),
903 for (good, val) in test_values:
904 cmd = basecmd + ["--ipolicy-%s=%s" % (par, val)]
905 AssertCommand(cmd, fail=not good)
908 # Check the affected parameter
909 (eff_policy, eff_specs) = _GetClusterIPolicy()
910 AssertEqual(eff_policy[disp_str].replace(" ", ""), curr_val)
911 # Check everything else
912 AssertEqual(eff_specs, old_specs)
913 for p in eff_policy.keys():
916 AssertEqual(eff_policy[p], old_policy[p])
919 def TestClusterSetISpecs(new_specs=None, diff_specs=None, fail=False,
921 """Change instance specs.
923 At most one of new_specs or diff_specs can be specified.
925 @type new_specs: dict
926 @param new_specs: new complete specs, in the same format returned by
927 L{_GetClusterIPolicy}
928 @type diff_specs: dict
929 @param diff_specs: partial specs, it can be an incomplete specifications, but
930 if min/max specs are specified, their number must match the number of the
933 @param fail: if the change is expected to fail
934 @type old_values: tuple
935 @param old_values: (old_policy, old_specs), as returned by
936 L{_GetClusterIPolicy}
937 @return: same as L{_GetClusterIPolicy}
940 build_cmd = lambda opts: ["gnt-cluster", "modify"] + opts
941 return qa_utils.TestSetISpecs(
942 new_specs=new_specs, diff_specs=diff_specs,
943 get_policy_fn=_GetClusterIPolicy, build_cmd_fn=build_cmd,
944 fail=fail, old_values=old_values)
947 def TestClusterModifyISpecs():
948 """gnt-cluster modify --specs-*"""
949 params = ["memory-size", "disk-size", "disk-count", "cpu-count", "nic-count"]
950 (cur_policy, cur_specs) = _GetClusterIPolicy()
951 # This test assumes that there is only one min/max bound
952 assert len(cur_specs[constants.ISPECS_MINMAX]) == 1
967 # This is to restore the old values
969 cur_specs[constants.ISPECS_MINMAX][0][constants.ISPECS_MIN][par],
970 cur_specs[constants.ISPECS_STD][par],
971 cur_specs[constants.ISPECS_MINMAX][0][constants.ISPECS_MAX][par])
973 for (good, mn, st, mx) in test_values:
975 constants.ISPECS_MINMAX: [{
976 constants.ISPECS_MIN: {par: mn},
977 constants.ISPECS_MAX: {par: mx}
979 constants.ISPECS_STD: {par: st}
981 cur_state = (cur_policy, cur_specs)
982 # We update cur_specs, as we've copied the values to restore already
983 (cur_policy, cur_specs) = TestClusterSetISpecs(
984 diff_specs=new_vals, fail=not good, old_values=cur_state)
986 # Get the ipolicy command
987 mnode = qa_config.GetMasterNode()
988 initcmd = GetCommandOutput(mnode.primary, "gnt-cluster show-ispecs-cmd")
989 modcmd = ["gnt-cluster", "modify"]
990 opts = initcmd.split()
991 assert opts[0:2] == ["gnt-cluster", "init"]
992 for k in range(2, len(opts) - 1):
993 if opts[k].startswith("--ipolicy-"):
994 assert k + 2 <= len(opts)
995 modcmd.extend(opts[k:k + 2])
996 # Re-apply the ipolicy (this should be a no-op)
997 AssertCommand(modcmd)
998 new_initcmd = GetCommandOutput(mnode.primary, "gnt-cluster show-ispecs-cmd")
999 AssertEqual(initcmd, new_initcmd)
1002 def TestClusterInfo():
1003 """gnt-cluster info"""
1004 AssertCommand(["gnt-cluster", "info"])
1007 def TestClusterRedistConf():
1008 """gnt-cluster redist-conf"""
1009 AssertCommand(["gnt-cluster", "redist-conf"])
1012 def TestClusterGetmaster():
1013 """gnt-cluster getmaster"""
1014 AssertCommand(["gnt-cluster", "getmaster"])
1017 def TestClusterVersion():
1018 """gnt-cluster version"""
1019 AssertCommand(["gnt-cluster", "version"])
1022 def TestClusterRenewCrypto():
1023 """gnt-cluster renew-crypto"""
1024 master = qa_config.GetMasterNode()
1026 # Conflicting options
1027 cmd = ["gnt-cluster", "renew-crypto", "--force",
1028 "--new-cluster-certificate", "--new-confd-hmac-key"]
1030 ["--new-rapi-certificate", "--rapi-certificate=/dev/null"],
1031 ["--new-cluster-domain-secret", "--cluster-domain-secret=/dev/null"],
1033 for i in conflicting:
1034 AssertCommand(cmd + i, fail=True)
1036 # Invalid RAPI certificate
1037 cmd = ["gnt-cluster", "renew-crypto", "--force",
1038 "--rapi-certificate=/dev/null"]
1039 AssertCommand(cmd, fail=True)
1041 rapi_cert_backup = qa_utils.BackupFile(master.primary,
1042 pathutils.RAPI_CERT_FILE)
1044 # Custom RAPI certificate
1045 fh = tempfile.NamedTemporaryFile()
1047 # Ensure certificate doesn't cause "gnt-cluster verify" to complain
1048 validity = constants.SSL_CERT_EXPIRATION_WARN * 3
1050 utils.GenerateSelfSignedSslCert(fh.name, validity=validity)
1052 tmpcert = qa_utils.UploadFile(master.primary, fh.name)
1054 AssertCommand(["gnt-cluster", "renew-crypto", "--force",
1055 "--rapi-certificate=%s" % tmpcert])
1057 AssertCommand(["rm", "-f", tmpcert])
1059 # Custom cluster domain secret
1060 cds_fh = tempfile.NamedTemporaryFile()
1061 cds_fh.write(utils.GenerateSecret())
1065 tmpcds = qa_utils.UploadFile(master.primary, cds_fh.name)
1067 AssertCommand(["gnt-cluster", "renew-crypto", "--force",
1068 "--cluster-domain-secret=%s" % tmpcds])
1070 AssertCommand(["rm", "-f", tmpcds])
1073 AssertCommand(["gnt-cluster", "renew-crypto", "--force",
1074 "--new-cluster-certificate", "--new-confd-hmac-key",
1075 "--new-rapi-certificate", "--new-cluster-domain-secret"])
1077 # Restore RAPI certificate
1078 AssertCommand(["gnt-cluster", "renew-crypto", "--force",
1079 "--rapi-certificate=%s" % rapi_cert_backup])
1081 AssertCommand(["rm", "-f", rapi_cert_backup])
1084 def TestClusterBurnin():
1086 master = qa_config.GetMasterNode()
1088 options = qa_config.get("options", {})
1089 disk_template = options.get("burnin-disk-template", constants.DT_DRBD8)
1090 parallel = options.get("burnin-in-parallel", False)
1091 check_inst = options.get("burnin-check-instances", False)
1092 do_rename = options.get("burnin-rename", "")
1093 do_reboot = options.get("burnin-reboot", True)
1094 reboot_types = options.get("reboot-types", constants.REBOOT_TYPES)
1096 # Get as many instances as we need
1100 num = qa_config.get("options", {}).get("burnin-instances", 1)
1101 for _ in range(0, num):
1102 instances.append(qa_config.AcquireInstance())
1103 except qa_error.OutOfInstancesError:
1104 print "Not enough instances, continuing anyway."
1106 if len(instances) < 1:
1107 raise qa_error.Error("Burnin needs at least one instance")
1109 script = qa_utils.UploadFile(master.primary, "../tools/burnin")
1111 disks = qa_config.GetDiskOptions()
1114 "--os=%s" % qa_config.get("os"),
1115 "--minmem-size=%s" % qa_config.get(constants.BE_MINMEM),
1116 "--maxmem-size=%s" % qa_config.get(constants.BE_MAXMEM),
1117 "--disk-size=%s" % ",".join([d.get("size") for d in disks]),
1118 "--disk-growth=%s" % ",".join([d.get("growth") for d in disks]),
1119 "--disk-template=%s" % disk_template]
1121 cmd.append("--parallel")
1122 cmd.append("--early-release")
1124 cmd.append("--http-check")
1126 cmd.append("--rename=%s" % do_rename)
1128 cmd.append("--no-reboot")
1130 cmd.append("--reboot-types=%s" % ",".join(reboot_types))
1131 cmd += [inst.name for inst in instances]
1134 AssertCommand(["rm", "-f", script])
1137 for inst in instances:
1141 def TestClusterMasterFailover():
1142 """gnt-cluster master-failover"""
1143 master = qa_config.GetMasterNode()
1144 failovermaster = qa_config.AcquireNode(exclude=master)
1146 cmd = ["gnt-cluster", "master-failover"]
1148 AssertCommand(cmd, node=failovermaster)
1149 # Back to original master node
1150 AssertCommand(cmd, node=master)
1152 failovermaster.Release()
1155 def _NodeQueueDrainFile(node):
1156 """Returns path to queue drain file for a node.
1159 return qa_utils.MakeNodePath(node, pathutils.JOB_QUEUE_DRAIN_FILE)
1162 def _AssertDrainFile(node, **kwargs):
1163 """Checks for the queue drain file.
1166 AssertCommand(["test", "-f", _NodeQueueDrainFile(node)], node=node, **kwargs)
1169 def TestClusterMasterFailoverWithDrainedQueue():
1170 """gnt-cluster master-failover with drained queue"""
1171 master = qa_config.GetMasterNode()
1172 failovermaster = qa_config.AcquireNode(exclude=master)
1174 # Ensure queue is not drained
1175 for node in [master, failovermaster]:
1176 _AssertDrainFile(node, fail=True)
1178 # Drain queue on failover master
1179 AssertCommand(["touch", _NodeQueueDrainFile(failovermaster)],
1180 node=failovermaster)
1182 cmd = ["gnt-cluster", "master-failover"]
1184 _AssertDrainFile(failovermaster)
1185 AssertCommand(cmd, node=failovermaster)
1186 _AssertDrainFile(master, fail=True)
1187 _AssertDrainFile(failovermaster, fail=True)
1189 # Back to original master node
1190 AssertCommand(cmd, node=master)
1192 failovermaster.Release()
1194 # Ensure queue is not drained
1195 for node in [master, failovermaster]:
1196 _AssertDrainFile(node, fail=True)
1199 def TestClusterCopyfile():
1200 """gnt-cluster copyfile"""
1201 master = qa_config.GetMasterNode()
1203 uniqueid = utils.NewUUID()
1205 # Create temporary file
1206 f = tempfile.NamedTemporaryFile()
1211 # Upload file to master node
1212 testname = qa_utils.UploadFile(master.primary, f.name)
1214 # Copy file to all nodes
1215 AssertCommand(["gnt-cluster", "copyfile", testname])
1216 _CheckFileOnAllNodes(testname, uniqueid)
1218 _RemoveFileFromAllNodes(testname)
1221 def TestClusterCommand():
1222 """gnt-cluster command"""
1223 uniqueid = utils.NewUUID()
1224 rfile = "/tmp/gnt%s" % utils.NewUUID()
1225 rcmd = utils.ShellQuoteArgs(["echo", "-n", uniqueid])
1226 cmd = utils.ShellQuoteArgs(["gnt-cluster", "command",
1227 "%s >%s" % (rcmd, rfile)])
1231 _CheckFileOnAllNodes(rfile, uniqueid)
1233 _RemoveFileFromAllNodes(rfile)
1236 def TestClusterDestroy():
1237 """gnt-cluster destroy"""
1238 AssertCommand(["gnt-cluster", "destroy", "--yes-do-it"])
1241 def TestClusterRepairDiskSizes():
1242 """gnt-cluster repair-disk-sizes"""
1243 AssertCommand(["gnt-cluster", "repair-disk-sizes"])
1246 def TestSetExclStorCluster(newvalue):
1247 """Set the exclusive_storage node parameter at the cluster level.
1249 @type newvalue: bool
1250 @param newvalue: New value of exclusive_storage
1252 @return: The old value of exclusive_storage
1255 es_path = ["Default node parameters", "exclusive_storage"]
1256 oldvalue = _GetClusterField(es_path)
1257 AssertCommand(["gnt-cluster", "modify", "--node-parameters",
1258 "exclusive_storage=%s" % newvalue])
1259 effvalue = _GetClusterField(es_path)
1260 if effvalue != newvalue:
1261 raise qa_error.Error("exclusive_storage has the wrong value: %s instead"
1262 " of %s" % (effvalue, newvalue))
1263 qa_config.SetExclusiveStorage(newvalue)
1267 def TestExclStorSharedPv(node):
1268 """cluster-verify reports LVs that share the same PV with exclusive_storage.
1271 vgname = qa_config.get("vg-name", constants.DEFAULT_VG)
1272 lvname1 = _QA_LV_PREFIX + "vol1"
1273 lvname2 = _QA_LV_PREFIX + "vol2"
1274 node_name = node.primary
1275 AssertCommand(["lvcreate", "-L1G", "-n", lvname1, vgname], node=node_name)
1276 AssertClusterVerify(fail=True, errors=[constants.CV_ENODEORPHANLV])
1277 AssertCommand(["lvcreate", "-L1G", "-n", lvname2, vgname], node=node_name)
1278 AssertClusterVerify(fail=True, errors=[constants.CV_ENODELVM,
1279 constants.CV_ENODEORPHANLV])
1280 AssertCommand(["lvremove", "-f", "/".join([vgname, lvname1])], node=node_name)
1281 AssertCommand(["lvremove", "-f", "/".join([vgname, lvname2])], node=node_name)
1282 AssertClusterVerify()