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")
229 cmd.extend(extra_args)
231 cmd.append(qa_config.get("name"))
235 cmd = ["gnt-cluster", "modify"]
237 # hypervisor parameter modifications
238 hvp = qa_config.get("hypervisor-parameters", {})
239 for k, v in hvp.items():
240 cmd.extend(["-H", "%s:%s" % (k, v)])
241 # backend parameter modifications
242 bep = qa_config.get("backend-parameters", "")
244 cmd.extend(["-B", bep])
250 osp = qa_config.get("os-parameters", {})
251 for k, v in osp.items():
252 AssertCommand(["gnt-os", "modify", "-O", v, k])
254 # OS hypervisor parameters
255 os_hvp = qa_config.get("os-hvp", {})
256 for os_name in os_hvp:
257 for hv, hvp in os_hvp[os_name].items():
258 AssertCommand(["gnt-os", "modify", "-H", "%s:%s" % (hv, hvp), os_name])
261 def TestClusterRename():
262 """gnt-cluster rename"""
263 cmd = ["gnt-cluster", "rename", "-f"]
265 original_name = qa_config.get("name")
266 rename_target = qa_config.get("rename", None)
267 if rename_target is None:
268 print qa_utils.FormatError('"rename" entry is missing')
272 cmd + [rename_target],
274 cmd + [original_name],
280 def TestClusterOob():
281 """out-of-band framework"""
282 oob_path_exists = "/tmp/ganeti-qa-oob-does-exist-%s" % utils.NewUUID()
284 AssertCommand(_CLUSTER_VERIFY)
285 AssertCommand(["gnt-cluster", "modify", "--node-parameters",
286 "oob_program=/tmp/ganeti-qa-oob-does-not-exist-%s" %
289 AssertCommand(_CLUSTER_VERIFY, fail=True)
291 AssertCommand(["touch", oob_path_exists])
292 AssertCommand(["chmod", "0400", oob_path_exists])
293 AssertCommand(["gnt-cluster", "copyfile", oob_path_exists])
296 AssertCommand(["gnt-cluster", "modify", "--node-parameters",
297 "oob_program=%s" % oob_path_exists])
299 AssertCommand(_CLUSTER_VERIFY, fail=True)
301 AssertCommand(["chmod", "0500", oob_path_exists])
302 AssertCommand(["gnt-cluster", "copyfile", oob_path_exists])
304 AssertCommand(_CLUSTER_VERIFY)
306 AssertCommand(["gnt-cluster", "command", "rm", oob_path_exists])
308 AssertCommand(["gnt-cluster", "modify", "--node-parameters",
312 def TestClusterEpo():
313 """gnt-cluster epo"""
314 master = qa_config.GetMasterNode()
316 # Assert that OOB is unavailable for all nodes
317 result_output = GetCommandOutput(master.primary,
318 "gnt-node list --verbose --no-headers -o"
320 AssertEqual(compat.all(powered == "(unavail)"
321 for powered in result_output.splitlines()), True)
324 AssertCommand(["gnt-cluster", "epo", "--groups", "--all"], fail=True)
325 # --all doesn't expect arguments
326 AssertCommand(["gnt-cluster", "epo", "--all", "some_arg"], fail=True)
328 # Unless --all is given master is not allowed to be in the list
329 AssertCommand(["gnt-cluster", "epo", "-f", master.primary], fail=True)
331 # This shouldn't fail
332 AssertCommand(["gnt-cluster", "epo", "-f", "--all"])
334 # All instances should have been stopped now
335 result_output = GetCommandOutput(master.primary,
336 "gnt-instance list --no-headers -o status")
337 # ERROR_down because the instance is stopped but not recorded as such
338 AssertEqual(compat.all(status == "ERROR_down"
339 for status in result_output.splitlines()), True)
341 # Now start everything again
342 AssertCommand(["gnt-cluster", "epo", "--on", "-f", "--all"])
344 # All instances should have been started now
345 result_output = GetCommandOutput(master.primary,
346 "gnt-instance list --no-headers -o status")
347 AssertEqual(compat.all(status == "running"
348 for status in result_output.splitlines()), True)
351 def TestClusterVerify():
352 """gnt-cluster verify"""
353 AssertCommand(_CLUSTER_VERIFY)
354 AssertCommand(["gnt-cluster", "verify-disks"])
357 def TestClusterVerifyDisksBrokenDRBD(instance, inst_nodes):
358 """gnt-cluster verify-disks with broken DRBD"""
359 qa_daemon.TestPauseWatcher()
362 info = qa_instance.GetInstanceInfo(instance.name)
363 snode = inst_nodes[1]
364 for idx, minor in enumerate(info["drbd-minors"][snode.primary]):
367 "(drbdsetup %d down >/dev/null 2>&1;" \
368 " drbdsetup down resource%d >/dev/null 2>&1) || /bin/true" % \
372 "(drbdsetup %d detach >/dev/null 2>&1;" \
373 " drbdsetup detach %d >/dev/null 2>&1) || /bin/true" % \
375 AssertCommand(break_drbd_cmd, node=snode)
377 verify_output = GetCommandOutput(qa_config.GetMasterNode().primary,
378 "gnt-cluster verify-disks")
379 activation_msg = "Activating disks for instance '%s'" % instance.name
380 if activation_msg not in verify_output:
381 raise qa_error.Error("gnt-cluster verify-disks did not activate broken"
382 " DRBD disks:\n%s" % verify_output)
384 verify_output = GetCommandOutput(qa_config.GetMasterNode().primary,
385 "gnt-cluster verify-disks")
386 if activation_msg in verify_output:
387 raise qa_error.Error("gnt-cluster verify-disks wants to activate broken"
388 " DRBD disks on second attempt:\n%s" % verify_output)
390 AssertCommand(_CLUSTER_VERIFY)
392 qa_daemon.TestResumeWatcher()
396 """gnt-debug test-jobqueue"""
397 AssertCommand(["gnt-debug", "test-jobqueue"])
401 """gnt-debug delay"""
402 AssertCommand(["gnt-debug", "delay", "1"])
403 AssertCommand(["gnt-debug", "delay", "--no-master", "1"])
404 AssertCommand(["gnt-debug", "delay", "--no-master",
405 "-n", node.primary, "1"])
408 def TestClusterReservedLvs():
409 """gnt-cluster reserved lvs"""
410 # if no lvm-based templates are supported, skip the test
411 if not qa_config.IsStorageTypeSupported(constants.ST_LVM_VG):
413 vgname = qa_config.get("vg-name", constants.DEFAULT_VG)
414 lvname = _QA_LV_PREFIX + "test"
415 lvfullname = "/".join([vgname, lvname])
417 (False, _CLUSTER_VERIFY),
418 (False, ["gnt-cluster", "modify", "--reserved-lvs", ""]),
419 (False, ["lvcreate", "-L1G", "-n", lvname, vgname]),
420 (True, _CLUSTER_VERIFY),
421 (False, ["gnt-cluster", "modify", "--reserved-lvs",
422 "%s,.*/other-test" % lvfullname]),
423 (False, _CLUSTER_VERIFY),
424 (False, ["gnt-cluster", "modify", "--reserved-lvs",
425 ".*/%s.*" % _QA_LV_PREFIX]),
426 (False, _CLUSTER_VERIFY),
427 (False, ["gnt-cluster", "modify", "--reserved-lvs", ""]),
428 (True, _CLUSTER_VERIFY),
429 (False, ["lvremove", "-f", lvfullname]),
430 (False, _CLUSTER_VERIFY),
432 AssertCommand(cmd, fail=fail)
435 def TestClusterModifyEmpty():
436 """gnt-cluster modify"""
437 AssertCommand(["gnt-cluster", "modify"], fail=True)
440 def TestClusterModifyDisk():
441 """gnt-cluster modify -D"""
442 for param in _FAIL_PARAMS:
443 AssertCommand(["gnt-cluster", "modify", "-D", param], fail=True)
446 def _GetOtherEnabledDiskTemplate(undesired_disk_templates,
447 enabled_disk_templates):
448 """Returns one template that is not in the undesired set.
450 @type undesired_disk_templates: list of string
451 @param undesired_disk_templates: a list of disk templates that we want to
452 exclude when drawing one disk template from the list of enabled
454 @type enabled_disk_templates: list of string
455 @param enabled_disk_templates: list of enabled disk templates (in QA)
458 desired_templates = list(set(enabled_disk_templates)
459 - set(undesired_disk_templates))
460 if desired_templates:
461 template = desired_templates[0]
463 # If no desired disk template is available for QA, choose 'diskless' and
465 template = constants.ST_DISKLESS
470 def TestClusterModifyFileBasedStorageDir(
471 file_disk_template, dir_config_key, default_dir, option_name):
472 """Tests gnt-cluster modify wrt to file-based directory options.
474 @type file_disk_template: string
475 @param file_disk_template: file-based disk template
476 @type dir_config_key: string
477 @param dir_config_key: key for the QA config to retrieve the default
479 @type default_dir: string
480 @param default_dir: default directory, if the QA config does not specify
482 @type option_name: string
483 @param option_name: name of the option of 'gnt-cluster modify' to
487 enabled_disk_templates = qa_config.GetEnabledDiskTemplates()
488 assert file_disk_template in [constants.DT_FILE, constants.DT_SHARED_FILE]
489 if not qa_config.IsTemplateSupported(file_disk_template):
492 # Get some non-file-based disk template to disable file storage
493 other_disk_template = _GetOtherEnabledDiskTemplate(
494 utils.storage.GetDiskTemplatesOfStorageType(constants.ST_FILE),
495 enabled_disk_templates)
497 file_storage_dir = qa_config.get(dir_config_key, default_dir)
498 invalid_file_storage_dir = "/boot/"
501 (False, ["gnt-cluster", "modify",
502 "--enabled-disk-templates=%s" % file_disk_template]),
503 (False, ["gnt-cluster", "modify",
504 "--%s=%s" % (option_name, file_storage_dir)]),
505 (False, ["gnt-cluster", "modify",
506 "--%s=%s" % (option_name, invalid_file_storage_dir)]),
507 # file storage dir is set to an inacceptable path, thus verify
509 (True, ["gnt-cluster", "verify"]),
510 # unsetting the storage dir while file storage is enabled
512 (True, ["gnt-cluster", "modify",
513 "--%s=" % option_name]),
514 (False, ["gnt-cluster", "modify",
515 "--%s=%s" % (option_name, file_storage_dir)]),
516 (False, ["gnt-cluster", "modify",
517 "--enabled-disk-templates=%s" % other_disk_template]),
518 (False, ["gnt-cluster", "modify",
519 "--%s=%s" % (option_name, invalid_file_storage_dir)]),
520 # file storage is set to an inacceptable path, but file storage
521 # is disabled, thus verify should not fail
522 (False, ["gnt-cluster", "verify"]),
523 # unsetting the file storage dir while file storage is not enabled
525 (False, ["gnt-cluster", "modify",
526 "--%s=" % option_name]),
527 # resetting everything to sane values
528 (False, ["gnt-cluster", "modify",
529 "--%s=%s" % (option_name, file_storage_dir),
530 "--enabled-disk-templates=%s" % ",".join(enabled_disk_templates)])
532 AssertCommand(cmd, fail=fail)
535 def TestClusterModifyFileStorageDir():
536 """gnt-cluster modify --file-storage-dir=..."""
537 TestClusterModifyFileBasedStorageDir(
538 constants.DT_FILE, "default-file-storage-dir",
539 pathutils.DEFAULT_FILE_STORAGE_DIR,
543 def TestClusterModifySharedFileStorageDir():
544 """gnt-cluster modify --shared-file-storage-dir=..."""
545 TestClusterModifyFileBasedStorageDir(
546 constants.DT_SHARED_FILE, "default-shared-file-storage-dir",
547 pathutils.DEFAULT_SHARED_FILE_STORAGE_DIR,
548 "shared-file-storage-dir")
551 def TestClusterModifyDiskTemplates():
552 """gnt-cluster modify --enabled-disk-templates=..."""
553 enabled_disk_templates = qa_config.GetEnabledDiskTemplates()
554 default_disk_template = qa_config.GetDefaultDiskTemplate()
556 _TestClusterModifyDiskTemplatesArguments(default_disk_template,
557 enabled_disk_templates)
558 _TestClusterModifyDiskTemplatesVgName(enabled_disk_templates)
560 _RestoreEnabledDiskTemplates()
561 nodes = qa_config.AcquireManyNodes(2)
563 instance_template = enabled_disk_templates[0]
564 instance = qa_instance.CreateInstanceByDiskTemplate(nodes, instance_template)
566 _TestClusterModifyUnusedDiskTemplate(instance_template)
567 _TestClusterModifyUsedDiskTemplate(instance_template,
568 enabled_disk_templates)
570 qa_instance.TestInstanceRemove(instance)
571 _RestoreEnabledDiskTemplates()
574 def _RestoreEnabledDiskTemplates():
575 """Sets the list of enabled disk templates back to the list of enabled disk
576 templates from the QA configuration. This can be used to make sure that
577 the tests that modify the list of disk templates do not interfere with
581 enabled_disk_templates = qa_config.GetEnabledDiskTemplates()
582 cmd = ["gnt-cluster", "modify",
583 "--enabled-disk-templates=%s" % ",".join(enabled_disk_templates),
584 "--ipolicy-disk-templates=%s" % ",".join(enabled_disk_templates),
587 if utils.IsLvmEnabled(qa_config.GetEnabledDiskTemplates()):
588 vgname = qa_config.get("vg-name", constants.DEFAULT_VG)
589 cmd.append("--vg-name=%s" % vgname)
591 AssertCommand(cmd, fail=False)
594 def _TestClusterModifyDiskTemplatesArguments(default_disk_template,
595 enabled_disk_templates):
596 """Tests argument handling of 'gnt-cluster modify' with respect to
597 the parameter '--enabled-disk-templates'. This test is independent
601 _RestoreEnabledDiskTemplates()
604 AssertCommand(["gnt-cluster", "modify",
605 "--enabled-disk-templates=pinkbunny"],
608 # duplicate entries do no harm
610 ["gnt-cluster", "modify",
611 "--enabled-disk-templates=%s,%s" %
612 (default_disk_template, default_disk_template),
613 "--ipolicy-disk-templates=%s" % default_disk_template],
616 if constants.DT_DRBD8 in enabled_disk_templates:
617 # interaction with --drbd-usermode-helper option
618 drbd_usermode_helper = qa_config.get("drbd-usermode-helper", None)
619 if not drbd_usermode_helper:
620 drbd_usermode_helper = "/bin/true"
621 # specifying a helper when drbd gets disabled is ok. Note that drbd still
622 # has to be installed on the nodes in this case
623 AssertCommand(["gnt-cluster", "modify",
624 "--drbd-usermode-helper=%s" % drbd_usermode_helper,
625 "--enabled-disk-templates=%s" % constants.DT_DISKLESS,
626 "--ipolicy-disk-templates=%s" % constants.DT_DISKLESS],
628 # specifying a helper when drbd is re-enabled
629 AssertCommand(["gnt-cluster", "modify",
630 "--drbd-usermode-helper=%s" % drbd_usermode_helper,
631 "--enabled-disk-templates=%s" %
632 ",".join(enabled_disk_templates),
633 "--ipolicy-disk-templates=%s" %
634 ",".join(enabled_disk_templates)],
638 def _TestClusterModifyDiskTemplatesVgName(enabled_disk_templates):
639 """Tests argument handling of 'gnt-cluster modify' with respect to
640 the parameter '--enabled-disk-templates' and '--vg-name'. This test is
641 independent of instances.
644 if not utils.IsLvmEnabled(enabled_disk_templates):
645 # These tests only make sense if lvm is enabled for QA
648 # determine an LVM and a non-LVM disk template for the tests
649 non_lvm_template = _GetOtherEnabledDiskTemplate(utils.GetLvmDiskTemplates(),
650 enabled_disk_templates)
651 lvm_template = list(set(enabled_disk_templates)
652 .intersection(set(utils.GetLvmDiskTemplates())))[0]
654 vgname = qa_config.get("vg-name", constants.DEFAULT_VG)
656 # Clean start: unset volume group name, disable lvm storage
658 ["gnt-cluster", "modify",
659 "--enabled-disk-templates=%s" % non_lvm_template,
660 "--ipolicy-disk-templates=%s" % non_lvm_template,
664 # Try to enable lvm, when no volume group is given
666 ["gnt-cluster", "modify",
667 "--enabled-disk-templates=%s" % lvm_template,
668 "--ipolicy-disk-templates=%s" % lvm_template],
671 # Set volume group, with lvm still disabled: just a warning
672 AssertCommand(["gnt-cluster", "modify", "--vg-name=%s" % vgname], fail=False)
674 # Try unsetting vg name and enabling lvm at the same time
676 ["gnt-cluster", "modify",
677 "--enabled-disk-templates=%s" % lvm_template,
678 "--ipolicy-disk-templates=%s" % lvm_template,
682 # Enable lvm with vg name present
684 ["gnt-cluster", "modify",
685 "--enabled-disk-templates=%s" % lvm_template,
686 "--ipolicy-disk-templates=%s" % lvm_template],
689 # Try unsetting vg name with lvm still enabled
690 AssertCommand(["gnt-cluster", "modify", "--vg-name="], fail=True)
692 # Disable lvm with vg name still set
694 ["gnt-cluster", "modify",
695 "--enabled-disk-templates=%s" % non_lvm_template,
696 "--ipolicy-disk-templates=%s" % non_lvm_template,
700 # Try unsetting vg name with lvm disabled
701 AssertCommand(["gnt-cluster", "modify", "--vg-name="], fail=False)
703 # Set vg name and enable lvm at the same time
705 ["gnt-cluster", "modify",
706 "--enabled-disk-templates=%s" % lvm_template,
707 "--ipolicy-disk-templates=%s" % lvm_template,
708 "--vg-name=%s" % vgname],
711 # Unset vg name and disable lvm at the same time
713 ["gnt-cluster", "modify",
714 "--enabled-disk-templates=%s" % non_lvm_template,
715 "--ipolicy-disk-templates=%s" % non_lvm_template,
719 _RestoreEnabledDiskTemplates()
722 def _TestClusterModifyUsedDiskTemplate(instance_template,
723 enabled_disk_templates):
724 """Tests that disk templates that are currently in use by instances cannot
725 be disabled on the cluster.
728 # If the list of enabled disk templates contains only one template
729 # we need to add some other templates, because the list of enabled disk
730 # templates can only be set to a non-empty list.
731 new_disk_templates = list(set(enabled_disk_templates)
732 - set([instance_template]))
733 if not new_disk_templates:
734 new_disk_templates = list(set([constants.DT_DISKLESS, constants.DT_BLOCK])
735 - set([instance_template]))
737 ["gnt-cluster", "modify",
738 "--enabled-disk-templates=%s" % ",".join(new_disk_templates),
739 "--ipolicy-disk-templates=%s" % ",".join(new_disk_templates)],
743 def _TestClusterModifyUnusedDiskTemplate(instance_template):
744 """Tests that unused disk templates can be disabled safely."""
745 all_disk_templates = constants.DISK_TEMPLATES
746 if not utils.IsLvmEnabled(qa_config.GetEnabledDiskTemplates()):
747 all_disk_templates = list(set(all_disk_templates) -
748 set(utils.GetLvmDiskTemplates()))
751 ["gnt-cluster", "modify",
752 "--enabled-disk-templates=%s" % ",".join(all_disk_templates),
753 "--ipolicy-disk-templates=%s" % ",".join(all_disk_templates)],
755 new_disk_templates = [instance_template]
757 ["gnt-cluster", "modify",
758 "--enabled-disk-templates=%s" % ",".join(new_disk_templates),
759 "--ipolicy-disk-templates=%s" % ",".join(new_disk_templates)],
763 def TestClusterModifyBe():
764 """gnt-cluster modify -B"""
767 (False, ["gnt-cluster", "modify", "-B", "maxmem=256"]),
768 (False, ["sh", "-c", "gnt-cluster info|grep '^ *maxmem: 256$'"]),
769 (False, ["gnt-cluster", "modify", "-B", "minmem=256"]),
770 (False, ["sh", "-c", "gnt-cluster info|grep '^ *minmem: 256$'"]),
771 (True, ["gnt-cluster", "modify", "-B", "maxmem=a"]),
772 (False, ["sh", "-c", "gnt-cluster info|grep '^ *maxmem: 256$'"]),
773 (True, ["gnt-cluster", "modify", "-B", "minmem=a"]),
774 (False, ["sh", "-c", "gnt-cluster info|grep '^ *minmem: 256$'"]),
775 (False, ["gnt-cluster", "modify", "-B", "maxmem=128,minmem=128"]),
776 (False, ["sh", "-c", "gnt-cluster info|grep '^ *maxmem: 128$'"]),
777 (False, ["sh", "-c", "gnt-cluster info|grep '^ *minmem: 128$'"]),
779 (False, ["gnt-cluster", "modify", "-B", "vcpus=4"]),
780 (False, ["sh", "-c", "gnt-cluster info|grep '^ *vcpus: 4$'"]),
781 (True, ["gnt-cluster", "modify", "-B", "vcpus=a"]),
782 (False, ["gnt-cluster", "modify", "-B", "vcpus=1"]),
783 (False, ["sh", "-c", "gnt-cluster info|grep '^ *vcpus: 1$'"]),
785 (False, ["gnt-cluster", "modify", "-B", "auto_balance=False"]),
786 (False, ["sh", "-c", "gnt-cluster info|grep '^ *auto_balance: False$'"]),
787 (True, ["gnt-cluster", "modify", "-B", "auto_balance=1"]),
788 (False, ["gnt-cluster", "modify", "-B", "auto_balance=True"]),
789 (False, ["sh", "-c", "gnt-cluster info|grep '^ *auto_balance: True$'"]),
791 AssertCommand(cmd, fail=fail)
793 # redo the original-requested BE parameters, if any
794 bep = qa_config.get("backend-parameters", "")
796 AssertCommand(["gnt-cluster", "modify", "-B", bep])
799 def _GetClusterIPolicy():
800 """Return the run-time values of the cluster-level instance policy.
803 @return: (policy, specs), where:
804 - policy is a dictionary of the policy values, instance specs excluded
805 - specs is a dictionary containing only the specs, using the internal
806 format (see L{constants.IPOLICY_DEFAULTS} for an example)
809 info = qa_utils.GetObjectInfo(["gnt-cluster", "info"])
810 policy = info["Instance policy - limits for instances"]
811 (ret_policy, ret_specs) = qa_utils.ParseIPolicy(policy)
814 assert "minmax" in ret_specs and "std" in ret_specs
815 assert len(ret_specs["minmax"]) > 0
816 assert len(ret_policy) > 0
817 return (ret_policy, ret_specs)
820 def TestClusterModifyIPolicy():
821 """gnt-cluster modify --ipolicy-*"""
822 basecmd = ["gnt-cluster", "modify"]
823 (old_policy, old_specs) = _GetClusterIPolicy()
824 for par in ["vcpu-ratio", "spindle-ratio"]:
825 curr_val = float(old_policy[par])
831 # Restore the old value
834 for (good, val) in test_values:
835 cmd = basecmd + ["--ipolicy-%s=%s" % (par, val)]
836 AssertCommand(cmd, fail=not good)
839 # Check the affected parameter
840 (eff_policy, eff_specs) = _GetClusterIPolicy()
841 AssertEqual(float(eff_policy[par]), curr_val)
842 # Check everything else
843 AssertEqual(eff_specs, old_specs)
844 for p in eff_policy.keys():
847 AssertEqual(eff_policy[p], old_policy[p])
849 # Disk templates are treated slightly differently
850 par = "disk-templates"
851 disp_str = "allowed disk templates"
852 curr_val = old_policy[disp_str]
854 (True, constants.DT_PLAIN),
855 (True, "%s,%s" % (constants.DT_PLAIN, constants.DT_DRBD8)),
856 (False, "thisisnotadisktemplate"),
858 # Restore the old value
859 (True, curr_val.replace(" ", "")),
861 for (good, val) in test_values:
862 cmd = basecmd + ["--ipolicy-%s=%s" % (par, val)]
863 AssertCommand(cmd, fail=not good)
866 # Check the affected parameter
867 (eff_policy, eff_specs) = _GetClusterIPolicy()
868 AssertEqual(eff_policy[disp_str].replace(" ", ""), curr_val)
869 # Check everything else
870 AssertEqual(eff_specs, old_specs)
871 for p in eff_policy.keys():
874 AssertEqual(eff_policy[p], old_policy[p])
877 def TestClusterSetISpecs(new_specs=None, diff_specs=None, fail=False,
879 """Change instance specs.
881 At most one of new_specs or diff_specs can be specified.
883 @type new_specs: dict
884 @param new_specs: new complete specs, in the same format returned by
885 L{_GetClusterIPolicy}
886 @type diff_specs: dict
887 @param diff_specs: partial specs, it can be an incomplete specifications, but
888 if min/max specs are specified, their number must match the number of the
891 @param fail: if the change is expected to fail
892 @type old_values: tuple
893 @param old_values: (old_policy, old_specs), as returned by
894 L{_GetClusterIPolicy}
895 @return: same as L{_GetClusterIPolicy}
898 build_cmd = lambda opts: ["gnt-cluster", "modify"] + opts
899 return qa_utils.TestSetISpecs(
900 new_specs=new_specs, diff_specs=diff_specs,
901 get_policy_fn=_GetClusterIPolicy, build_cmd_fn=build_cmd,
902 fail=fail, old_values=old_values)
905 def TestClusterModifyISpecs():
906 """gnt-cluster modify --specs-*"""
907 params = ["memory-size", "disk-size", "disk-count", "cpu-count", "nic-count"]
908 (cur_policy, cur_specs) = _GetClusterIPolicy()
909 # This test assumes that there is only one min/max bound
910 assert len(cur_specs[constants.ISPECS_MINMAX]) == 1
925 # This is to restore the old values
927 cur_specs[constants.ISPECS_MINMAX][0][constants.ISPECS_MIN][par],
928 cur_specs[constants.ISPECS_STD][par],
929 cur_specs[constants.ISPECS_MINMAX][0][constants.ISPECS_MAX][par])
931 for (good, mn, st, mx) in test_values:
933 constants.ISPECS_MINMAX: [{
934 constants.ISPECS_MIN: {par: mn},
935 constants.ISPECS_MAX: {par: mx}
937 constants.ISPECS_STD: {par: st}
939 cur_state = (cur_policy, cur_specs)
940 # We update cur_specs, as we've copied the values to restore already
941 (cur_policy, cur_specs) = TestClusterSetISpecs(
942 diff_specs=new_vals, fail=not good, old_values=cur_state)
944 # Get the ipolicy command
945 mnode = qa_config.GetMasterNode()
946 initcmd = GetCommandOutput(mnode.primary, "gnt-cluster show-ispecs-cmd")
947 modcmd = ["gnt-cluster", "modify"]
948 opts = initcmd.split()
949 assert opts[0:2] == ["gnt-cluster", "init"]
950 for k in range(2, len(opts) - 1):
951 if opts[k].startswith("--ipolicy-"):
952 assert k + 2 <= len(opts)
953 modcmd.extend(opts[k:k + 2])
954 # Re-apply the ipolicy (this should be a no-op)
955 AssertCommand(modcmd)
956 new_initcmd = GetCommandOutput(mnode.primary, "gnt-cluster show-ispecs-cmd")
957 AssertEqual(initcmd, new_initcmd)
960 def TestClusterInfo():
961 """gnt-cluster info"""
962 AssertCommand(["gnt-cluster", "info"])
965 def TestClusterRedistConf():
966 """gnt-cluster redist-conf"""
967 AssertCommand(["gnt-cluster", "redist-conf"])
970 def TestClusterGetmaster():
971 """gnt-cluster getmaster"""
972 AssertCommand(["gnt-cluster", "getmaster"])
975 def TestClusterVersion():
976 """gnt-cluster version"""
977 AssertCommand(["gnt-cluster", "version"])
980 def TestClusterRenewCrypto():
981 """gnt-cluster renew-crypto"""
982 master = qa_config.GetMasterNode()
984 # Conflicting options
985 cmd = ["gnt-cluster", "renew-crypto", "--force",
986 "--new-cluster-certificate", "--new-confd-hmac-key"]
988 ["--new-rapi-certificate", "--rapi-certificate=/dev/null"],
989 ["--new-cluster-domain-secret", "--cluster-domain-secret=/dev/null"],
991 for i in conflicting:
992 AssertCommand(cmd + i, fail=True)
994 # Invalid RAPI certificate
995 cmd = ["gnt-cluster", "renew-crypto", "--force",
996 "--rapi-certificate=/dev/null"]
997 AssertCommand(cmd, fail=True)
999 rapi_cert_backup = qa_utils.BackupFile(master.primary,
1000 pathutils.RAPI_CERT_FILE)
1002 # Custom RAPI certificate
1003 fh = tempfile.NamedTemporaryFile()
1005 # Ensure certificate doesn't cause "gnt-cluster verify" to complain
1006 validity = constants.SSL_CERT_EXPIRATION_WARN * 3
1008 utils.GenerateSelfSignedSslCert(fh.name, validity=validity)
1010 tmpcert = qa_utils.UploadFile(master.primary, fh.name)
1012 AssertCommand(["gnt-cluster", "renew-crypto", "--force",
1013 "--rapi-certificate=%s" % tmpcert])
1015 AssertCommand(["rm", "-f", tmpcert])
1017 # Custom cluster domain secret
1018 cds_fh = tempfile.NamedTemporaryFile()
1019 cds_fh.write(utils.GenerateSecret())
1023 tmpcds = qa_utils.UploadFile(master.primary, cds_fh.name)
1025 AssertCommand(["gnt-cluster", "renew-crypto", "--force",
1026 "--cluster-domain-secret=%s" % tmpcds])
1028 AssertCommand(["rm", "-f", tmpcds])
1031 AssertCommand(["gnt-cluster", "renew-crypto", "--force",
1032 "--new-cluster-certificate", "--new-confd-hmac-key",
1033 "--new-rapi-certificate", "--new-cluster-domain-secret"])
1035 # Restore RAPI certificate
1036 AssertCommand(["gnt-cluster", "renew-crypto", "--force",
1037 "--rapi-certificate=%s" % rapi_cert_backup])
1039 AssertCommand(["rm", "-f", rapi_cert_backup])
1042 def TestClusterBurnin():
1044 master = qa_config.GetMasterNode()
1046 options = qa_config.get("options", {})
1047 disk_template = options.get("burnin-disk-template", constants.DT_DRBD8)
1048 parallel = options.get("burnin-in-parallel", False)
1049 check_inst = options.get("burnin-check-instances", False)
1050 do_rename = options.get("burnin-rename", "")
1051 do_reboot = options.get("burnin-reboot", True)
1052 reboot_types = options.get("reboot-types", constants.REBOOT_TYPES)
1054 # Get as many instances as we need
1058 num = qa_config.get("options", {}).get("burnin-instances", 1)
1059 for _ in range(0, num):
1060 instances.append(qa_config.AcquireInstance())
1061 except qa_error.OutOfInstancesError:
1062 print "Not enough instances, continuing anyway."
1064 if len(instances) < 1:
1065 raise qa_error.Error("Burnin needs at least one instance")
1067 script = qa_utils.UploadFile(master.primary, "../tools/burnin")
1069 disks = qa_config.GetDiskOptions()
1072 "--os=%s" % qa_config.get("os"),
1073 "--minmem-size=%s" % qa_config.get(constants.BE_MINMEM),
1074 "--maxmem-size=%s" % qa_config.get(constants.BE_MAXMEM),
1075 "--disk-size=%s" % ",".join([d.get("size") for d in disks]),
1076 "--disk-growth=%s" % ",".join([d.get("growth") for d in disks]),
1077 "--disk-template=%s" % disk_template]
1079 cmd.append("--parallel")
1080 cmd.append("--early-release")
1082 cmd.append("--http-check")
1084 cmd.append("--rename=%s" % do_rename)
1086 cmd.append("--no-reboot")
1088 cmd.append("--reboot-types=%s" % ",".join(reboot_types))
1089 cmd += [inst.name for inst in instances]
1092 AssertCommand(["rm", "-f", script])
1095 for inst in instances:
1099 def TestClusterMasterFailover():
1100 """gnt-cluster master-failover"""
1101 master = qa_config.GetMasterNode()
1102 failovermaster = qa_config.AcquireNode(exclude=master)
1104 cmd = ["gnt-cluster", "master-failover"]
1106 AssertCommand(cmd, node=failovermaster)
1107 # Back to original master node
1108 AssertCommand(cmd, node=master)
1110 failovermaster.Release()
1113 def _NodeQueueDrainFile(node):
1114 """Returns path to queue drain file for a node.
1117 return qa_utils.MakeNodePath(node, pathutils.JOB_QUEUE_DRAIN_FILE)
1120 def _AssertDrainFile(node, **kwargs):
1121 """Checks for the queue drain file.
1124 AssertCommand(["test", "-f", _NodeQueueDrainFile(node)], node=node, **kwargs)
1127 def TestClusterMasterFailoverWithDrainedQueue():
1128 """gnt-cluster master-failover with drained queue"""
1129 master = qa_config.GetMasterNode()
1130 failovermaster = qa_config.AcquireNode(exclude=master)
1132 # Ensure queue is not drained
1133 for node in [master, failovermaster]:
1134 _AssertDrainFile(node, fail=True)
1136 # Drain queue on failover master
1137 AssertCommand(["touch", _NodeQueueDrainFile(failovermaster)],
1138 node=failovermaster)
1140 cmd = ["gnt-cluster", "master-failover"]
1142 _AssertDrainFile(failovermaster)
1143 AssertCommand(cmd, node=failovermaster)
1144 _AssertDrainFile(master, fail=True)
1145 _AssertDrainFile(failovermaster, fail=True)
1147 # Back to original master node
1148 AssertCommand(cmd, node=master)
1150 failovermaster.Release()
1152 # Ensure queue is not drained
1153 for node in [master, failovermaster]:
1154 _AssertDrainFile(node, fail=True)
1157 def TestClusterCopyfile():
1158 """gnt-cluster copyfile"""
1159 master = qa_config.GetMasterNode()
1161 uniqueid = utils.NewUUID()
1163 # Create temporary file
1164 f = tempfile.NamedTemporaryFile()
1169 # Upload file to master node
1170 testname = qa_utils.UploadFile(master.primary, f.name)
1172 # Copy file to all nodes
1173 AssertCommand(["gnt-cluster", "copyfile", testname])
1174 _CheckFileOnAllNodes(testname, uniqueid)
1176 _RemoveFileFromAllNodes(testname)
1179 def TestClusterCommand():
1180 """gnt-cluster command"""
1181 uniqueid = utils.NewUUID()
1182 rfile = "/tmp/gnt%s" % utils.NewUUID()
1183 rcmd = utils.ShellQuoteArgs(["echo", "-n", uniqueid])
1184 cmd = utils.ShellQuoteArgs(["gnt-cluster", "command",
1185 "%s >%s" % (rcmd, rfile)])
1189 _CheckFileOnAllNodes(rfile, uniqueid)
1191 _RemoveFileFromAllNodes(rfile)
1194 def TestClusterDestroy():
1195 """gnt-cluster destroy"""
1196 AssertCommand(["gnt-cluster", "destroy", "--yes-do-it"])
1199 def TestClusterRepairDiskSizes():
1200 """gnt-cluster repair-disk-sizes"""
1201 AssertCommand(["gnt-cluster", "repair-disk-sizes"])
1204 def TestSetExclStorCluster(newvalue):
1205 """Set the exclusive_storage node parameter at the cluster level.
1207 @type newvalue: bool
1208 @param newvalue: New value of exclusive_storage
1210 @return: The old value of exclusive_storage
1213 es_path = ["Default node parameters", "exclusive_storage"]
1214 oldvalue = _GetClusterField(es_path)
1215 AssertCommand(["gnt-cluster", "modify", "--node-parameters",
1216 "exclusive_storage=%s" % newvalue])
1217 effvalue = _GetClusterField(es_path)
1218 if effvalue != newvalue:
1219 raise qa_error.Error("exclusive_storage has the wrong value: %s instead"
1220 " of %s" % (effvalue, newvalue))
1221 qa_config.SetExclusiveStorage(newvalue)
1225 def TestExclStorSharedPv(node):
1226 """cluster-verify reports LVs that share the same PV with exclusive_storage.
1229 vgname = qa_config.get("vg-name", constants.DEFAULT_VG)
1230 lvname1 = _QA_LV_PREFIX + "vol1"
1231 lvname2 = _QA_LV_PREFIX + "vol2"
1232 node_name = node.primary
1233 AssertCommand(["lvcreate", "-L1G", "-n", lvname1, vgname], node=node_name)
1234 AssertClusterVerify(fail=True, errors=[constants.CV_ENODEORPHANLV])
1235 AssertCommand(["lvcreate", "-L1G", "-n", lvname2, vgname], node=node_name)
1236 AssertClusterVerify(fail=True, errors=[constants.CV_ENODELVM,
1237 constants.CV_ENODEORPHANLV])
1238 AssertCommand(["lvremove", "-f", "/".join([vgname, lvname1])], node=node_name)
1239 AssertCommand(["lvremove", "-f", "/".join([vgname, lvname2])], node=node_name)
1240 AssertClusterVerify()