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 cmd = ["gnt-cluster", "modify", "--enabled-disk-templates=%s" %
582 ",".join(qa_config.GetEnabledDiskTemplates())]
584 if utils.IsLvmEnabled(qa_config.GetEnabledDiskTemplates()):
585 vgname = qa_config.get("vg-name", constants.DEFAULT_VG)
586 cmd.append("--vg-name=%s" % vgname)
588 AssertCommand(cmd, fail=False)
591 def _TestClusterModifyDiskTemplatesArguments(default_disk_template,
592 enabled_disk_templates):
593 """Tests argument handling of 'gnt-cluster modify' with respect to
594 the parameter '--enabled-disk-templates'. This test is independent
598 _RestoreEnabledDiskTemplates()
601 AssertCommand(["gnt-cluster", "modify",
602 "--enabled-disk-templates=pinkbunny"],
605 # duplicate entries do no harm
607 ["gnt-cluster", "modify",
608 "--enabled-disk-templates=%s,%s" %
609 (default_disk_template, default_disk_template)],
612 if constants.DT_DRBD8 in enabled_disk_templates:
613 # interaction with --drbd-usermode-helper option
614 drbd_usermode_helper = qa_config.get("drbd-usermode-helper", None)
615 if not drbd_usermode_helper:
616 drbd_usermode_helper = "/bin/true"
617 # specifying a helper when drbd gets disabled is ok. Note that drbd still
618 # has to be installed on the nodes in this case
619 AssertCommand(["gnt-cluster", "modify",
620 "--drbd-usermode-helper=%s" % drbd_usermode_helper,
621 "--enabled-disk-templates=%s" % constants.DT_DISKLESS],
623 # specifying a helper when drbd is re-enabled
624 AssertCommand(["gnt-cluster", "modify",
625 "--drbd-usermode-helper=%s" % drbd_usermode_helper,
626 "--enabled-disk-templates=%s" %
627 ",".join(enabled_disk_templates)],
631 def _TestClusterModifyDiskTemplatesVgName(enabled_disk_templates):
632 """Tests argument handling of 'gnt-cluster modify' with respect to
633 the parameter '--enabled-disk-templates' and '--vg-name'. This test is
634 independent of instances.
637 if not utils.IsLvmEnabled(enabled_disk_templates):
638 # These tests only make sense if lvm is enabled for QA
641 # determine an LVM and a non-LVM disk template for the tests
642 non_lvm_template = _GetOtherEnabledDiskTemplate(utils.GetLvmDiskTemplates(),
643 enabled_disk_templates)
644 lvm_template = list(set(enabled_disk_templates)
645 .intersection(set(utils.GetLvmDiskTemplates())))[0]
647 vgname = qa_config.get("vg-name", constants.DEFAULT_VG)
649 # Clean start: unset volume group name, disable lvm storage
651 ["gnt-cluster", "modify",
652 "--enabled-disk-templates=%s" % non_lvm_template,
656 # Try to enable lvm, when no volume group is given
658 ["gnt-cluster", "modify",
659 "--enabled-disk-templates=%s" % lvm_template],
662 # Set volume group, with lvm still disabled: just a warning
663 AssertCommand(["gnt-cluster", "modify", "--vg-name=%s" % vgname], fail=False)
665 # Try unsetting vg name and enabling lvm at the same time
667 ["gnt-cluster", "modify",
668 "--enabled-disk-templates=%s" % lvm_template,
672 # Enable lvm with vg name present
674 ["gnt-cluster", "modify",
675 "--enabled-disk-templates=%s" % lvm_template],
678 # Try unsetting vg name with lvm still enabled
679 AssertCommand(["gnt-cluster", "modify", "--vg-name="], fail=True)
681 # Disable lvm with vg name still set
683 ["gnt-cluster", "modify", "--enabled-disk-templates=%s" % non_lvm_template],
686 # Try unsetting vg name with lvm disabled
687 AssertCommand(["gnt-cluster", "modify", "--vg-name="], fail=False)
689 # Set vg name and enable lvm at the same time
691 ["gnt-cluster", "modify",
692 "--enabled-disk-templates=%s" % lvm_template,
693 "--vg-name=%s" % vgname],
696 # Unset vg name and disable lvm at the same time
698 ["gnt-cluster", "modify",
699 "--enabled-disk-templates=%s" % non_lvm_template,
703 _RestoreEnabledDiskTemplates()
706 def _TestClusterModifyUsedDiskTemplate(instance_template,
707 enabled_disk_templates):
708 """Tests that disk templates that are currently in use by instances cannot
709 be disabled on the cluster.
712 # If the list of enabled disk templates contains only one template
713 # we need to add some other templates, because the list of enabled disk
714 # templates can only be set to a non-empty list.
715 new_disk_templates = list(set(enabled_disk_templates)
716 - set([instance_template]))
717 if not new_disk_templates:
718 new_disk_templates = list(set([constants.DT_DISKLESS, constants.DT_BLOCK])
719 - set([instance_template]))
721 ["gnt-cluster", "modify",
722 "--enabled-disk-templates=%s" %
723 ",".join(new_disk_templates)],
727 def _TestClusterModifyUnusedDiskTemplate(instance_template):
728 """Tests that unused disk templates can be disabled safely."""
729 all_disk_templates = constants.DISK_TEMPLATES
730 if not utils.IsLvmEnabled(qa_config.GetEnabledDiskTemplates()):
731 all_disk_templates = list(set(all_disk_templates) -
732 set(utils.GetLvmDiskTemplates()))
735 ["gnt-cluster", "modify",
736 "--enabled-disk-templates=%s" %
737 ",".join(all_disk_templates)],
739 new_disk_templates = [instance_template]
741 ["gnt-cluster", "modify",
742 "--enabled-disk-templates=%s" %
743 ",".join(new_disk_templates)],
747 def TestClusterModifyBe():
748 """gnt-cluster modify -B"""
751 (False, ["gnt-cluster", "modify", "-B", "maxmem=256"]),
752 (False, ["sh", "-c", "gnt-cluster info|grep '^ *maxmem: 256$'"]),
753 (False, ["gnt-cluster", "modify", "-B", "minmem=256"]),
754 (False, ["sh", "-c", "gnt-cluster info|grep '^ *minmem: 256$'"]),
755 (True, ["gnt-cluster", "modify", "-B", "maxmem=a"]),
756 (False, ["sh", "-c", "gnt-cluster info|grep '^ *maxmem: 256$'"]),
757 (True, ["gnt-cluster", "modify", "-B", "minmem=a"]),
758 (False, ["sh", "-c", "gnt-cluster info|grep '^ *minmem: 256$'"]),
759 (False, ["gnt-cluster", "modify", "-B", "maxmem=128,minmem=128"]),
760 (False, ["sh", "-c", "gnt-cluster info|grep '^ *maxmem: 128$'"]),
761 (False, ["sh", "-c", "gnt-cluster info|grep '^ *minmem: 128$'"]),
763 (False, ["gnt-cluster", "modify", "-B", "vcpus=4"]),
764 (False, ["sh", "-c", "gnt-cluster info|grep '^ *vcpus: 4$'"]),
765 (True, ["gnt-cluster", "modify", "-B", "vcpus=a"]),
766 (False, ["gnt-cluster", "modify", "-B", "vcpus=1"]),
767 (False, ["sh", "-c", "gnt-cluster info|grep '^ *vcpus: 1$'"]),
769 (False, ["gnt-cluster", "modify", "-B", "auto_balance=False"]),
770 (False, ["sh", "-c", "gnt-cluster info|grep '^ *auto_balance: False$'"]),
771 (True, ["gnt-cluster", "modify", "-B", "auto_balance=1"]),
772 (False, ["gnt-cluster", "modify", "-B", "auto_balance=True"]),
773 (False, ["sh", "-c", "gnt-cluster info|grep '^ *auto_balance: True$'"]),
775 AssertCommand(cmd, fail=fail)
777 # redo the original-requested BE parameters, if any
778 bep = qa_config.get("backend-parameters", "")
780 AssertCommand(["gnt-cluster", "modify", "-B", bep])
783 def _GetClusterIPolicy():
784 """Return the run-time values of the cluster-level instance policy.
787 @return: (policy, specs), where:
788 - policy is a dictionary of the policy values, instance specs excluded
789 - specs is a dictionary containing only the specs, using the internal
790 format (see L{constants.IPOLICY_DEFAULTS} for an example)
793 info = qa_utils.GetObjectInfo(["gnt-cluster", "info"])
794 policy = info["Instance policy - limits for instances"]
795 (ret_policy, ret_specs) = qa_utils.ParseIPolicy(policy)
798 assert "minmax" in ret_specs and "std" in ret_specs
799 assert len(ret_specs["minmax"]) > 0
800 assert len(ret_policy) > 0
801 return (ret_policy, ret_specs)
804 def TestClusterModifyIPolicy():
805 """gnt-cluster modify --ipolicy-*"""
806 basecmd = ["gnt-cluster", "modify"]
807 (old_policy, old_specs) = _GetClusterIPolicy()
808 for par in ["vcpu-ratio", "spindle-ratio"]:
809 curr_val = float(old_policy[par])
815 # Restore the old value
818 for (good, val) in test_values:
819 cmd = basecmd + ["--ipolicy-%s=%s" % (par, val)]
820 AssertCommand(cmd, fail=not good)
823 # Check the affected parameter
824 (eff_policy, eff_specs) = _GetClusterIPolicy()
825 AssertEqual(float(eff_policy[par]), curr_val)
826 # Check everything else
827 AssertEqual(eff_specs, old_specs)
828 for p in eff_policy.keys():
831 AssertEqual(eff_policy[p], old_policy[p])
833 # Disk templates are treated slightly differently
834 par = "disk-templates"
835 disp_str = "allowed disk templates"
836 curr_val = old_policy[disp_str]
838 (True, constants.DT_PLAIN),
839 (True, "%s,%s" % (constants.DT_PLAIN, constants.DT_DRBD8)),
840 (False, "thisisnotadisktemplate"),
842 # Restore the old value
843 (True, curr_val.replace(" ", "")),
845 for (good, val) in test_values:
846 cmd = basecmd + ["--ipolicy-%s=%s" % (par, val)]
847 AssertCommand(cmd, fail=not good)
850 # Check the affected parameter
851 (eff_policy, eff_specs) = _GetClusterIPolicy()
852 AssertEqual(eff_policy[disp_str].replace(" ", ""), curr_val)
853 # Check everything else
854 AssertEqual(eff_specs, old_specs)
855 for p in eff_policy.keys():
858 AssertEqual(eff_policy[p], old_policy[p])
861 def TestClusterSetISpecs(new_specs=None, diff_specs=None, fail=False,
863 """Change instance specs.
865 At most one of new_specs or diff_specs can be specified.
867 @type new_specs: dict
868 @param new_specs: new complete specs, in the same format returned by
869 L{_GetClusterIPolicy}
870 @type diff_specs: dict
871 @param diff_specs: partial specs, it can be an incomplete specifications, but
872 if min/max specs are specified, their number must match the number of the
875 @param fail: if the change is expected to fail
876 @type old_values: tuple
877 @param old_values: (old_policy, old_specs), as returned by
878 L{_GetClusterIPolicy}
879 @return: same as L{_GetClusterIPolicy}
882 build_cmd = lambda opts: ["gnt-cluster", "modify"] + opts
883 return qa_utils.TestSetISpecs(
884 new_specs=new_specs, diff_specs=diff_specs,
885 get_policy_fn=_GetClusterIPolicy, build_cmd_fn=build_cmd,
886 fail=fail, old_values=old_values)
889 def TestClusterModifyISpecs():
890 """gnt-cluster modify --specs-*"""
891 params = ["memory-size", "disk-size", "disk-count", "cpu-count", "nic-count"]
892 (cur_policy, cur_specs) = _GetClusterIPolicy()
893 # This test assumes that there is only one min/max bound
894 assert len(cur_specs[constants.ISPECS_MINMAX]) == 1
909 # This is to restore the old values
911 cur_specs[constants.ISPECS_MINMAX][0][constants.ISPECS_MIN][par],
912 cur_specs[constants.ISPECS_STD][par],
913 cur_specs[constants.ISPECS_MINMAX][0][constants.ISPECS_MAX][par])
915 for (good, mn, st, mx) in test_values:
917 constants.ISPECS_MINMAX: [{
918 constants.ISPECS_MIN: {par: mn},
919 constants.ISPECS_MAX: {par: mx}
921 constants.ISPECS_STD: {par: st}
923 cur_state = (cur_policy, cur_specs)
924 # We update cur_specs, as we've copied the values to restore already
925 (cur_policy, cur_specs) = TestClusterSetISpecs(
926 diff_specs=new_vals, fail=not good, old_values=cur_state)
928 # Get the ipolicy command
929 mnode = qa_config.GetMasterNode()
930 initcmd = GetCommandOutput(mnode.primary, "gnt-cluster show-ispecs-cmd")
931 modcmd = ["gnt-cluster", "modify"]
932 opts = initcmd.split()
933 assert opts[0:2] == ["gnt-cluster", "init"]
934 for k in range(2, len(opts) - 1):
935 if opts[k].startswith("--ipolicy-"):
936 assert k + 2 <= len(opts)
937 modcmd.extend(opts[k:k + 2])
938 # Re-apply the ipolicy (this should be a no-op)
939 AssertCommand(modcmd)
940 new_initcmd = GetCommandOutput(mnode.primary, "gnt-cluster show-ispecs-cmd")
941 AssertEqual(initcmd, new_initcmd)
944 def TestClusterInfo():
945 """gnt-cluster info"""
946 AssertCommand(["gnt-cluster", "info"])
949 def TestClusterRedistConf():
950 """gnt-cluster redist-conf"""
951 AssertCommand(["gnt-cluster", "redist-conf"])
954 def TestClusterGetmaster():
955 """gnt-cluster getmaster"""
956 AssertCommand(["gnt-cluster", "getmaster"])
959 def TestClusterVersion():
960 """gnt-cluster version"""
961 AssertCommand(["gnt-cluster", "version"])
964 def TestClusterRenewCrypto():
965 """gnt-cluster renew-crypto"""
966 master = qa_config.GetMasterNode()
968 # Conflicting options
969 cmd = ["gnt-cluster", "renew-crypto", "--force",
970 "--new-cluster-certificate", "--new-confd-hmac-key"]
972 ["--new-rapi-certificate", "--rapi-certificate=/dev/null"],
973 ["--new-cluster-domain-secret", "--cluster-domain-secret=/dev/null"],
975 for i in conflicting:
976 AssertCommand(cmd + i, fail=True)
978 # Invalid RAPI certificate
979 cmd = ["gnt-cluster", "renew-crypto", "--force",
980 "--rapi-certificate=/dev/null"]
981 AssertCommand(cmd, fail=True)
983 rapi_cert_backup = qa_utils.BackupFile(master.primary,
984 pathutils.RAPI_CERT_FILE)
986 # Custom RAPI certificate
987 fh = tempfile.NamedTemporaryFile()
989 # Ensure certificate doesn't cause "gnt-cluster verify" to complain
990 validity = constants.SSL_CERT_EXPIRATION_WARN * 3
992 utils.GenerateSelfSignedSslCert(fh.name, validity=validity)
994 tmpcert = qa_utils.UploadFile(master.primary, fh.name)
996 AssertCommand(["gnt-cluster", "renew-crypto", "--force",
997 "--rapi-certificate=%s" % tmpcert])
999 AssertCommand(["rm", "-f", tmpcert])
1001 # Custom cluster domain secret
1002 cds_fh = tempfile.NamedTemporaryFile()
1003 cds_fh.write(utils.GenerateSecret())
1007 tmpcds = qa_utils.UploadFile(master.primary, cds_fh.name)
1009 AssertCommand(["gnt-cluster", "renew-crypto", "--force",
1010 "--cluster-domain-secret=%s" % tmpcds])
1012 AssertCommand(["rm", "-f", tmpcds])
1015 AssertCommand(["gnt-cluster", "renew-crypto", "--force",
1016 "--new-cluster-certificate", "--new-confd-hmac-key",
1017 "--new-rapi-certificate", "--new-cluster-domain-secret"])
1019 # Restore RAPI certificate
1020 AssertCommand(["gnt-cluster", "renew-crypto", "--force",
1021 "--rapi-certificate=%s" % rapi_cert_backup])
1023 AssertCommand(["rm", "-f", rapi_cert_backup])
1026 def TestClusterBurnin():
1028 master = qa_config.GetMasterNode()
1030 options = qa_config.get("options", {})
1031 disk_template = options.get("burnin-disk-template", constants.DT_DRBD8)
1032 parallel = options.get("burnin-in-parallel", False)
1033 check_inst = options.get("burnin-check-instances", False)
1034 do_rename = options.get("burnin-rename", "")
1035 do_reboot = options.get("burnin-reboot", True)
1036 reboot_types = options.get("reboot-types", constants.REBOOT_TYPES)
1038 # Get as many instances as we need
1042 num = qa_config.get("options", {}).get("burnin-instances", 1)
1043 for _ in range(0, num):
1044 instances.append(qa_config.AcquireInstance())
1045 except qa_error.OutOfInstancesError:
1046 print "Not enough instances, continuing anyway."
1048 if len(instances) < 1:
1049 raise qa_error.Error("Burnin needs at least one instance")
1051 script = qa_utils.UploadFile(master.primary, "../tools/burnin")
1053 disks = qa_config.GetDiskOptions()
1056 "--os=%s" % qa_config.get("os"),
1057 "--minmem-size=%s" % qa_config.get(constants.BE_MINMEM),
1058 "--maxmem-size=%s" % qa_config.get(constants.BE_MAXMEM),
1059 "--disk-size=%s" % ",".join([d.get("size") for d in disks]),
1060 "--disk-growth=%s" % ",".join([d.get("growth") for d in disks]),
1061 "--disk-template=%s" % disk_template]
1063 cmd.append("--parallel")
1064 cmd.append("--early-release")
1066 cmd.append("--http-check")
1068 cmd.append("--rename=%s" % do_rename)
1070 cmd.append("--no-reboot")
1072 cmd.append("--reboot-types=%s" % ",".join(reboot_types))
1073 cmd += [inst.name for inst in instances]
1076 AssertCommand(["rm", "-f", script])
1079 for inst in instances:
1083 def TestClusterMasterFailover():
1084 """gnt-cluster master-failover"""
1085 master = qa_config.GetMasterNode()
1086 failovermaster = qa_config.AcquireNode(exclude=master)
1088 cmd = ["gnt-cluster", "master-failover"]
1090 AssertCommand(cmd, node=failovermaster)
1091 # Back to original master node
1092 AssertCommand(cmd, node=master)
1094 failovermaster.Release()
1097 def _NodeQueueDrainFile(node):
1098 """Returns path to queue drain file for a node.
1101 return qa_utils.MakeNodePath(node, pathutils.JOB_QUEUE_DRAIN_FILE)
1104 def _AssertDrainFile(node, **kwargs):
1105 """Checks for the queue drain file.
1108 AssertCommand(["test", "-f", _NodeQueueDrainFile(node)], node=node, **kwargs)
1111 def TestClusterMasterFailoverWithDrainedQueue():
1112 """gnt-cluster master-failover with drained queue"""
1113 master = qa_config.GetMasterNode()
1114 failovermaster = qa_config.AcquireNode(exclude=master)
1116 # Ensure queue is not drained
1117 for node in [master, failovermaster]:
1118 _AssertDrainFile(node, fail=True)
1120 # Drain queue on failover master
1121 AssertCommand(["touch", _NodeQueueDrainFile(failovermaster)],
1122 node=failovermaster)
1124 cmd = ["gnt-cluster", "master-failover"]
1126 _AssertDrainFile(failovermaster)
1127 AssertCommand(cmd, node=failovermaster)
1128 _AssertDrainFile(master, fail=True)
1129 _AssertDrainFile(failovermaster, fail=True)
1131 # Back to original master node
1132 AssertCommand(cmd, node=master)
1134 failovermaster.Release()
1136 # Ensure queue is not drained
1137 for node in [master, failovermaster]:
1138 _AssertDrainFile(node, fail=True)
1141 def TestClusterCopyfile():
1142 """gnt-cluster copyfile"""
1143 master = qa_config.GetMasterNode()
1145 uniqueid = utils.NewUUID()
1147 # Create temporary file
1148 f = tempfile.NamedTemporaryFile()
1153 # Upload file to master node
1154 testname = qa_utils.UploadFile(master.primary, f.name)
1156 # Copy file to all nodes
1157 AssertCommand(["gnt-cluster", "copyfile", testname])
1158 _CheckFileOnAllNodes(testname, uniqueid)
1160 _RemoveFileFromAllNodes(testname)
1163 def TestClusterCommand():
1164 """gnt-cluster command"""
1165 uniqueid = utils.NewUUID()
1166 rfile = "/tmp/gnt%s" % utils.NewUUID()
1167 rcmd = utils.ShellQuoteArgs(["echo", "-n", uniqueid])
1168 cmd = utils.ShellQuoteArgs(["gnt-cluster", "command",
1169 "%s >%s" % (rcmd, rfile)])
1173 _CheckFileOnAllNodes(rfile, uniqueid)
1175 _RemoveFileFromAllNodes(rfile)
1178 def TestClusterDestroy():
1179 """gnt-cluster destroy"""
1180 AssertCommand(["gnt-cluster", "destroy", "--yes-do-it"])
1183 def TestClusterRepairDiskSizes():
1184 """gnt-cluster repair-disk-sizes"""
1185 AssertCommand(["gnt-cluster", "repair-disk-sizes"])
1188 def TestSetExclStorCluster(newvalue):
1189 """Set the exclusive_storage node parameter at the cluster level.
1191 @type newvalue: bool
1192 @param newvalue: New value of exclusive_storage
1194 @return: The old value of exclusive_storage
1197 es_path = ["Default node parameters", "exclusive_storage"]
1198 oldvalue = _GetClusterField(es_path)
1199 AssertCommand(["gnt-cluster", "modify", "--node-parameters",
1200 "exclusive_storage=%s" % newvalue])
1201 effvalue = _GetClusterField(es_path)
1202 if effvalue != newvalue:
1203 raise qa_error.Error("exclusive_storage has the wrong value: %s instead"
1204 " of %s" % (effvalue, newvalue))
1205 qa_config.SetExclusiveStorage(newvalue)
1209 def TestExclStorSharedPv(node):
1210 """cluster-verify reports LVs that share the same PV with exclusive_storage.
1213 vgname = qa_config.get("vg-name", constants.DEFAULT_VG)
1214 lvname1 = _QA_LV_PREFIX + "vol1"
1215 lvname2 = _QA_LV_PREFIX + "vol2"
1216 node_name = node.primary
1217 AssertCommand(["lvcreate", "-L1G", "-n", lvname1, vgname], node=node_name)
1218 AssertClusterVerify(fail=True, errors=[constants.CV_ENODEORPHANLV])
1219 AssertCommand(["lvcreate", "-L1G", "-n", lvname2, vgname], node=node_name)
1220 AssertClusterVerify(fail=True, errors=[constants.CV_ENODELVM,
1221 constants.CV_ENODEORPHANLV])
1222 AssertCommand(["lvremove", "-f", "/".join([vgname, lvname1])], node=node_name)
1223 AssertCommand(["lvremove", "-f", "/".join([vgname, lvname2])], node=node_name)
1224 AssertClusterVerify()