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 vgname = qa_config.get("vg-name", constants.DEFAULT_VG)
411 lvname = _QA_LV_PREFIX + "test"
412 lvfullname = "/".join([vgname, lvname])
414 (False, _CLUSTER_VERIFY),
415 (False, ["gnt-cluster", "modify", "--reserved-lvs", ""]),
416 (False, ["lvcreate", "-L1G", "-n", lvname, vgname]),
417 (True, _CLUSTER_VERIFY),
418 (False, ["gnt-cluster", "modify", "--reserved-lvs",
419 "%s,.*/other-test" % lvfullname]),
420 (False, _CLUSTER_VERIFY),
421 (False, ["gnt-cluster", "modify", "--reserved-lvs",
422 ".*/%s.*" % _QA_LV_PREFIX]),
423 (False, _CLUSTER_VERIFY),
424 (False, ["gnt-cluster", "modify", "--reserved-lvs", ""]),
425 (True, _CLUSTER_VERIFY),
426 (False, ["lvremove", "-f", lvfullname]),
427 (False, _CLUSTER_VERIFY),
429 AssertCommand(cmd, fail=fail)
432 def TestClusterModifyEmpty():
433 """gnt-cluster modify"""
434 AssertCommand(["gnt-cluster", "modify"], fail=True)
437 def TestClusterModifyDisk():
438 """gnt-cluster modify -D"""
439 for param in _FAIL_PARAMS:
440 AssertCommand(["gnt-cluster", "modify", "-D", param], fail=True)
443 def _GetOtherEnabledDiskTemplate(undesired_disk_templates,
444 enabled_disk_templates):
445 """Returns one template that is not in the undesired set.
447 @type undesired_disk_templates: list of string
448 @param undesired_disk_templates: a list of disk templates that we want to
449 exclude when drawing one disk template from the list of enabled
451 @type enabled_disk_templates: list of string
452 @param enabled_disk_templates: list of enabled disk templates (in QA)
455 desired_templates = list(set(enabled_disk_templates)
456 - set(undesired_disk_templates))
457 if desired_templates:
458 template = desired_templates[0]
460 # If no desired disk template is available for QA, choose 'diskless' and
462 template = constants.ST_DISKLESS
467 def TestClusterModifyFileBasedStorageDir(
468 file_disk_template, dir_config_key, default_dir, option_name):
469 """Tests gnt-cluster modify wrt to file-based directory options.
471 @type file_disk_template: string
472 @param file_disk_template: file-based disk template
473 @type dir_config_key: string
474 @param dir_config_key: key for the QA config to retrieve the default
476 @type default_dir: string
477 @param default_dir: default directory, if the QA config does not specify
479 @type option_name: string
480 @param option_name: name of the option of 'gnt-cluster modify' to
484 enabled_disk_templates = qa_config.GetEnabledDiskTemplates()
485 assert file_disk_template in [constants.DT_FILE, constants.DT_SHARED_FILE]
486 if not qa_config.IsTemplateSupported(file_disk_template):
489 # Get some non-file-based disk template to disable file storage
490 other_disk_template = _GetOtherEnabledDiskTemplate(
491 utils.storage.GetDiskTemplatesOfStorageType(constants.ST_FILE),
492 enabled_disk_templates)
494 file_storage_dir = qa_config.get(dir_config_key, default_dir)
495 invalid_file_storage_dir = "/boot/"
498 (False, ["gnt-cluster", "modify",
499 "--enabled-disk-templates=%s" % file_disk_template]),
500 (False, ["gnt-cluster", "modify",
501 "--%s=%s" % (option_name, file_storage_dir)]),
502 (False, ["gnt-cluster", "modify",
503 "--%s=%s" % (option_name, invalid_file_storage_dir)]),
504 # file storage dir is set to an inacceptable path, thus verify
506 (True, ["gnt-cluster", "verify"]),
507 # unsetting the storage dir while file storage is enabled
509 (True, ["gnt-cluster", "modify",
510 "--%s=" % option_name]),
511 (False, ["gnt-cluster", "modify",
512 "--%s=%s" % (option_name, file_storage_dir)]),
513 (False, ["gnt-cluster", "modify",
514 "--enabled-disk-templates=%s" % other_disk_template]),
515 (False, ["gnt-cluster", "modify",
516 "--%s=%s" % (option_name, invalid_file_storage_dir)]),
517 # file storage is set to an inacceptable path, but file storage
518 # is disabled, thus verify should not fail
519 (False, ["gnt-cluster", "verify"]),
520 # unsetting the file storage dir while file storage is not enabled
522 (False, ["gnt-cluster", "modify",
523 "--%s=" % option_name]),
524 # resetting everything to sane values
525 (False, ["gnt-cluster", "modify",
526 "--%s=%s" % (option_name, file_storage_dir),
527 "--enabled-disk-templates=%s" % ",".join(enabled_disk_templates)])
529 AssertCommand(cmd, fail=fail)
532 def TestClusterModifyFileStorageDir():
533 """gnt-cluster modify --file-storage-dir=..."""
534 TestClusterModifyFileBasedStorageDir(
535 constants.DT_FILE, "default-file-storage-dir",
536 pathutils.DEFAULT_FILE_STORAGE_DIR,
540 def TestClusterModifySharedFileStorageDir():
541 """gnt-cluster modify --shared-file-storage-dir=..."""
542 TestClusterModifyFileBasedStorageDir(
543 constants.DT_SHARED_FILE, "default-shared-file-storage-dir",
544 pathutils.DEFAULT_SHARED_FILE_STORAGE_DIR,
545 "shared-file-storage-dir")
548 def TestClusterModifyDiskTemplates():
549 """gnt-cluster modify --enabled-disk-templates=..."""
550 enabled_disk_templates = qa_config.GetEnabledDiskTemplates()
551 default_disk_template = qa_config.GetDefaultDiskTemplate()
553 _TestClusterModifyDiskTemplatesArguments(default_disk_template,
554 enabled_disk_templates)
555 _TestClusterModifyDiskTemplatesVgName(enabled_disk_templates)
557 _RestoreEnabledDiskTemplates()
558 nodes = qa_config.AcquireManyNodes(2)
560 instance_template = enabled_disk_templates[0]
561 instance = qa_instance.CreateInstanceByDiskTemplate(nodes, instance_template)
563 _TestClusterModifyUnusedDiskTemplate(instance_template)
564 _TestClusterModifyUsedDiskTemplate(instance_template,
565 enabled_disk_templates)
567 qa_instance.TestInstanceRemove(instance)
568 _RestoreEnabledDiskTemplates()
571 def _RestoreEnabledDiskTemplates():
572 """Sets the list of enabled disk templates back to the list of enabled disk
573 templates from the QA configuration. This can be used to make sure that
574 the tests that modify the list of disk templates do not interfere with
578 cmd = ["gnt-cluster", "modify", "--enabled-disk-templates=%s" %
579 ",".join(qa_config.GetEnabledDiskTemplates())]
581 if utils.IsLvmEnabled(qa_config.GetEnabledDiskTemplates()):
582 vgname = qa_config.get("vg-name", constants.DEFAULT_VG)
583 cmd.append("--vg-name=%s" % vgname)
585 AssertCommand(cmd, fail=False)
588 def _TestClusterModifyDiskTemplatesArguments(default_disk_template,
589 enabled_disk_templates):
590 """Tests argument handling of 'gnt-cluster modify' with respect to
591 the parameter '--enabled-disk-templates'. This test is independent
595 _RestoreEnabledDiskTemplates()
598 AssertCommand(["gnt-cluster", "modify",
599 "--enabled-disk-templates=pinkbunny"],
602 # duplicate entries do no harm
604 ["gnt-cluster", "modify",
605 "--enabled-disk-templates=%s,%s" %
606 (default_disk_template, default_disk_template)],
609 if constants.DT_DRBD8 in enabled_disk_templates:
610 # interaction with --drbd-usermode-helper option
611 drbd_usermode_helper = qa_config.get("drbd-usermode-helper", None)
612 if not drbd_usermode_helper:
613 drbd_usermode_helper = "/bin/true"
614 # specifying a helper when drbd gets disabled is ok. Note that drbd still
615 # has to be installed on the nodes in this case
616 AssertCommand(["gnt-cluster", "modify",
617 "--drbd-usermode-helper=%s" % drbd_usermode_helper,
618 "--enabled-disk-templates=%s" % constants.DT_DISKLESS],
620 # specifying a helper when drbd is re-enabled
621 AssertCommand(["gnt-cluster", "modify",
622 "--drbd-usermode-helper=%s" % drbd_usermode_helper,
623 "--enabled-disk-templates=%s" %
624 ",".join(enabled_disk_templates)],
628 def _TestClusterModifyDiskTemplatesVgName(enabled_disk_templates):
629 """Tests argument handling of 'gnt-cluster modify' with respect to
630 the parameter '--enabled-disk-templates' and '--vg-name'. This test is
631 independent of instances.
634 if not utils.IsLvmEnabled(enabled_disk_templates):
635 # These tests only make sense if lvm is enabled for QA
638 # determine an LVM and a non-LVM disk template for the tests
639 non_lvm_template = _GetOtherEnabledDiskTemplate(utils.GetLvmDiskTemplates(),
640 enabled_disk_templates)
641 lvm_template = list(set(enabled_disk_templates)
642 .intersection(set(utils.GetLvmDiskTemplates())))[0]
644 vgname = qa_config.get("vg-name", constants.DEFAULT_VG)
646 # Clean start: unset volume group name, disable lvm storage
648 ["gnt-cluster", "modify",
649 "--enabled-disk-templates=%s" % non_lvm_template,
653 # Try to enable lvm, when no volume group is given
655 ["gnt-cluster", "modify",
656 "--enabled-disk-templates=%s" % lvm_template],
659 # Set volume group, with lvm still disabled: just a warning
660 AssertCommand(["gnt-cluster", "modify", "--vg-name=%s" % vgname], fail=False)
662 # Try unsetting vg name and enabling lvm at the same time
664 ["gnt-cluster", "modify",
665 "--enabled-disk-templates=%s" % lvm_template,
669 # Enable lvm with vg name present
671 ["gnt-cluster", "modify",
672 "--enabled-disk-templates=%s" % lvm_template],
675 # Try unsetting vg name with lvm still enabled
676 AssertCommand(["gnt-cluster", "modify", "--vg-name="], fail=True)
678 # Disable lvm with vg name still set
680 ["gnt-cluster", "modify", "--enabled-disk-templates=%s" % non_lvm_template],
683 # Try unsetting vg name with lvm disabled
684 AssertCommand(["gnt-cluster", "modify", "--vg-name="], fail=False)
686 # Set vg name and enable lvm at the same time
688 ["gnt-cluster", "modify",
689 "--enabled-disk-templates=%s" % lvm_template,
690 "--vg-name=%s" % vgname],
693 # Unset vg name and disable lvm at the same time
695 ["gnt-cluster", "modify",
696 "--enabled-disk-templates=%s" % non_lvm_template,
700 _RestoreEnabledDiskTemplates()
703 def _TestClusterModifyUsedDiskTemplate(instance_template,
704 enabled_disk_templates):
705 """Tests that disk templates that are currently in use by instances cannot
706 be disabled on the cluster.
709 # If the list of enabled disk templates contains only one template
710 # we need to add some other templates, because the list of enabled disk
711 # templates can only be set to a non-empty list.
712 new_disk_templates = list(set(enabled_disk_templates)
713 - set([instance_template]))
714 if not new_disk_templates:
715 new_disk_templates = list(set([constants.DT_DISKLESS, constants.DT_BLOCK])
716 - set([instance_template]))
718 ["gnt-cluster", "modify",
719 "--enabled-disk-templates=%s" %
720 ",".join(new_disk_templates)],
724 def _TestClusterModifyUnusedDiskTemplate(instance_template):
725 """Tests that unused disk templates can be disabled safely."""
726 all_disk_templates = constants.DISK_TEMPLATES
727 if not utils.IsLvmEnabled(qa_config.GetEnabledDiskTemplates()):
728 all_disk_templates = list(set(all_disk_templates) -
729 set(utils.GetLvmDiskTemplates()))
732 ["gnt-cluster", "modify",
733 "--enabled-disk-templates=%s" %
734 ",".join(all_disk_templates)],
736 new_disk_templates = [instance_template]
738 ["gnt-cluster", "modify",
739 "--enabled-disk-templates=%s" %
740 ",".join(new_disk_templates)],
744 def TestClusterModifyBe():
745 """gnt-cluster modify -B"""
748 (False, ["gnt-cluster", "modify", "-B", "maxmem=256"]),
749 (False, ["sh", "-c", "gnt-cluster info|grep '^ *maxmem: 256$'"]),
750 (False, ["gnt-cluster", "modify", "-B", "minmem=256"]),
751 (False, ["sh", "-c", "gnt-cluster info|grep '^ *minmem: 256$'"]),
752 (True, ["gnt-cluster", "modify", "-B", "maxmem=a"]),
753 (False, ["sh", "-c", "gnt-cluster info|grep '^ *maxmem: 256$'"]),
754 (True, ["gnt-cluster", "modify", "-B", "minmem=a"]),
755 (False, ["sh", "-c", "gnt-cluster info|grep '^ *minmem: 256$'"]),
756 (False, ["gnt-cluster", "modify", "-B", "maxmem=128,minmem=128"]),
757 (False, ["sh", "-c", "gnt-cluster info|grep '^ *maxmem: 128$'"]),
758 (False, ["sh", "-c", "gnt-cluster info|grep '^ *minmem: 128$'"]),
760 (False, ["gnt-cluster", "modify", "-B", "vcpus=4"]),
761 (False, ["sh", "-c", "gnt-cluster info|grep '^ *vcpus: 4$'"]),
762 (True, ["gnt-cluster", "modify", "-B", "vcpus=a"]),
763 (False, ["gnt-cluster", "modify", "-B", "vcpus=1"]),
764 (False, ["sh", "-c", "gnt-cluster info|grep '^ *vcpus: 1$'"]),
766 (False, ["gnt-cluster", "modify", "-B", "auto_balance=False"]),
767 (False, ["sh", "-c", "gnt-cluster info|grep '^ *auto_balance: False$'"]),
768 (True, ["gnt-cluster", "modify", "-B", "auto_balance=1"]),
769 (False, ["gnt-cluster", "modify", "-B", "auto_balance=True"]),
770 (False, ["sh", "-c", "gnt-cluster info|grep '^ *auto_balance: True$'"]),
772 AssertCommand(cmd, fail=fail)
774 # redo the original-requested BE parameters, if any
775 bep = qa_config.get("backend-parameters", "")
777 AssertCommand(["gnt-cluster", "modify", "-B", bep])
780 def _GetClusterIPolicy():
781 """Return the run-time values of the cluster-level instance policy.
784 @return: (policy, specs), where:
785 - policy is a dictionary of the policy values, instance specs excluded
786 - specs is a dictionary containing only the specs, using the internal
787 format (see L{constants.IPOLICY_DEFAULTS} for an example)
790 info = qa_utils.GetObjectInfo(["gnt-cluster", "info"])
791 policy = info["Instance policy - limits for instances"]
792 (ret_policy, ret_specs) = qa_utils.ParseIPolicy(policy)
795 assert "minmax" in ret_specs and "std" in ret_specs
796 assert len(ret_specs["minmax"]) > 0
797 assert len(ret_policy) > 0
798 return (ret_policy, ret_specs)
801 def TestClusterModifyIPolicy():
802 """gnt-cluster modify --ipolicy-*"""
803 basecmd = ["gnt-cluster", "modify"]
804 (old_policy, old_specs) = _GetClusterIPolicy()
805 for par in ["vcpu-ratio", "spindle-ratio"]:
806 curr_val = float(old_policy[par])
812 # Restore the old value
815 for (good, val) in test_values:
816 cmd = basecmd + ["--ipolicy-%s=%s" % (par, val)]
817 AssertCommand(cmd, fail=not good)
820 # Check the affected parameter
821 (eff_policy, eff_specs) = _GetClusterIPolicy()
822 AssertEqual(float(eff_policy[par]), curr_val)
823 # Check everything else
824 AssertEqual(eff_specs, old_specs)
825 for p in eff_policy.keys():
828 AssertEqual(eff_policy[p], old_policy[p])
830 # Disk templates are treated slightly differently
831 par = "disk-templates"
832 disp_str = "allowed disk templates"
833 curr_val = old_policy[disp_str]
835 (True, constants.DT_PLAIN),
836 (True, "%s,%s" % (constants.DT_PLAIN, constants.DT_DRBD8)),
837 (False, "thisisnotadisktemplate"),
839 # Restore the old value
840 (True, curr_val.replace(" ", "")),
842 for (good, val) in test_values:
843 cmd = basecmd + ["--ipolicy-%s=%s" % (par, val)]
844 AssertCommand(cmd, fail=not good)
847 # Check the affected parameter
848 (eff_policy, eff_specs) = _GetClusterIPolicy()
849 AssertEqual(eff_policy[disp_str].replace(" ", ""), curr_val)
850 # Check everything else
851 AssertEqual(eff_specs, old_specs)
852 for p in eff_policy.keys():
855 AssertEqual(eff_policy[p], old_policy[p])
858 def TestClusterSetISpecs(new_specs=None, diff_specs=None, fail=False,
860 """Change instance specs.
862 At most one of new_specs or diff_specs can be specified.
864 @type new_specs: dict
865 @param new_specs: new complete specs, in the same format returned by
866 L{_GetClusterIPolicy}
867 @type diff_specs: dict
868 @param diff_specs: partial specs, it can be an incomplete specifications, but
869 if min/max specs are specified, their number must match the number of the
872 @param fail: if the change is expected to fail
873 @type old_values: tuple
874 @param old_values: (old_policy, old_specs), as returned by
875 L{_GetClusterIPolicy}
876 @return: same as L{_GetClusterIPolicy}
879 build_cmd = lambda opts: ["gnt-cluster", "modify"] + opts
880 return qa_utils.TestSetISpecs(
881 new_specs=new_specs, diff_specs=diff_specs,
882 get_policy_fn=_GetClusterIPolicy, build_cmd_fn=build_cmd,
883 fail=fail, old_values=old_values)
886 def TestClusterModifyISpecs():
887 """gnt-cluster modify --specs-*"""
888 params = ["memory-size", "disk-size", "disk-count", "cpu-count", "nic-count"]
889 (cur_policy, cur_specs) = _GetClusterIPolicy()
890 # This test assumes that there is only one min/max bound
891 assert len(cur_specs[constants.ISPECS_MINMAX]) == 1
906 # This is to restore the old values
908 cur_specs[constants.ISPECS_MINMAX][0][constants.ISPECS_MIN][par],
909 cur_specs[constants.ISPECS_STD][par],
910 cur_specs[constants.ISPECS_MINMAX][0][constants.ISPECS_MAX][par])
912 for (good, mn, st, mx) in test_values:
914 constants.ISPECS_MINMAX: [{
915 constants.ISPECS_MIN: {par: mn},
916 constants.ISPECS_MAX: {par: mx}
918 constants.ISPECS_STD: {par: st}
920 cur_state = (cur_policy, cur_specs)
921 # We update cur_specs, as we've copied the values to restore already
922 (cur_policy, cur_specs) = TestClusterSetISpecs(
923 diff_specs=new_vals, fail=not good, old_values=cur_state)
925 # Get the ipolicy command
926 mnode = qa_config.GetMasterNode()
927 initcmd = GetCommandOutput(mnode.primary, "gnt-cluster show-ispecs-cmd")
928 modcmd = ["gnt-cluster", "modify"]
929 opts = initcmd.split()
930 assert opts[0:2] == ["gnt-cluster", "init"]
931 for k in range(2, len(opts) - 1):
932 if opts[k].startswith("--ipolicy-"):
933 assert k + 2 <= len(opts)
934 modcmd.extend(opts[k:k + 2])
935 # Re-apply the ipolicy (this should be a no-op)
936 AssertCommand(modcmd)
937 new_initcmd = GetCommandOutput(mnode.primary, "gnt-cluster show-ispecs-cmd")
938 AssertEqual(initcmd, new_initcmd)
941 def TestClusterInfo():
942 """gnt-cluster info"""
943 AssertCommand(["gnt-cluster", "info"])
946 def TestClusterRedistConf():
947 """gnt-cluster redist-conf"""
948 AssertCommand(["gnt-cluster", "redist-conf"])
951 def TestClusterGetmaster():
952 """gnt-cluster getmaster"""
953 AssertCommand(["gnt-cluster", "getmaster"])
956 def TestClusterVersion():
957 """gnt-cluster version"""
958 AssertCommand(["gnt-cluster", "version"])
961 def TestClusterRenewCrypto():
962 """gnt-cluster renew-crypto"""
963 master = qa_config.GetMasterNode()
965 # Conflicting options
966 cmd = ["gnt-cluster", "renew-crypto", "--force",
967 "--new-cluster-certificate", "--new-confd-hmac-key"]
969 ["--new-rapi-certificate", "--rapi-certificate=/dev/null"],
970 ["--new-cluster-domain-secret", "--cluster-domain-secret=/dev/null"],
972 for i in conflicting:
973 AssertCommand(cmd + i, fail=True)
975 # Invalid RAPI certificate
976 cmd = ["gnt-cluster", "renew-crypto", "--force",
977 "--rapi-certificate=/dev/null"]
978 AssertCommand(cmd, fail=True)
980 rapi_cert_backup = qa_utils.BackupFile(master.primary,
981 pathutils.RAPI_CERT_FILE)
983 # Custom RAPI certificate
984 fh = tempfile.NamedTemporaryFile()
986 # Ensure certificate doesn't cause "gnt-cluster verify" to complain
987 validity = constants.SSL_CERT_EXPIRATION_WARN * 3
989 utils.GenerateSelfSignedSslCert(fh.name, validity=validity)
991 tmpcert = qa_utils.UploadFile(master.primary, fh.name)
993 AssertCommand(["gnt-cluster", "renew-crypto", "--force",
994 "--rapi-certificate=%s" % tmpcert])
996 AssertCommand(["rm", "-f", tmpcert])
998 # Custom cluster domain secret
999 cds_fh = tempfile.NamedTemporaryFile()
1000 cds_fh.write(utils.GenerateSecret())
1004 tmpcds = qa_utils.UploadFile(master.primary, cds_fh.name)
1006 AssertCommand(["gnt-cluster", "renew-crypto", "--force",
1007 "--cluster-domain-secret=%s" % tmpcds])
1009 AssertCommand(["rm", "-f", tmpcds])
1012 AssertCommand(["gnt-cluster", "renew-crypto", "--force",
1013 "--new-cluster-certificate", "--new-confd-hmac-key",
1014 "--new-rapi-certificate", "--new-cluster-domain-secret"])
1016 # Restore RAPI certificate
1017 AssertCommand(["gnt-cluster", "renew-crypto", "--force",
1018 "--rapi-certificate=%s" % rapi_cert_backup])
1020 AssertCommand(["rm", "-f", rapi_cert_backup])
1023 def TestClusterBurnin():
1025 master = qa_config.GetMasterNode()
1027 options = qa_config.get("options", {})
1028 disk_template = options.get("burnin-disk-template", constants.DT_DRBD8)
1029 parallel = options.get("burnin-in-parallel", False)
1030 check_inst = options.get("burnin-check-instances", False)
1031 do_rename = options.get("burnin-rename", "")
1032 do_reboot = options.get("burnin-reboot", True)
1033 reboot_types = options.get("reboot-types", constants.REBOOT_TYPES)
1035 # Get as many instances as we need
1039 num = qa_config.get("options", {}).get("burnin-instances", 1)
1040 for _ in range(0, num):
1041 instances.append(qa_config.AcquireInstance())
1042 except qa_error.OutOfInstancesError:
1043 print "Not enough instances, continuing anyway."
1045 if len(instances) < 1:
1046 raise qa_error.Error("Burnin needs at least one instance")
1048 script = qa_utils.UploadFile(master.primary, "../tools/burnin")
1050 disks = qa_config.GetDiskOptions()
1053 "--os=%s" % qa_config.get("os"),
1054 "--minmem-size=%s" % qa_config.get(constants.BE_MINMEM),
1055 "--maxmem-size=%s" % qa_config.get(constants.BE_MAXMEM),
1056 "--disk-size=%s" % ",".join([d.get("size") for d in disks]),
1057 "--disk-growth=%s" % ",".join([d.get("growth") for d in disks]),
1058 "--disk-template=%s" % disk_template]
1060 cmd.append("--parallel")
1061 cmd.append("--early-release")
1063 cmd.append("--http-check")
1065 cmd.append("--rename=%s" % do_rename)
1067 cmd.append("--no-reboot")
1069 cmd.append("--reboot-types=%s" % ",".join(reboot_types))
1070 cmd += [inst.name for inst in instances]
1073 AssertCommand(["rm", "-f", script])
1076 for inst in instances:
1080 def TestClusterMasterFailover():
1081 """gnt-cluster master-failover"""
1082 master = qa_config.GetMasterNode()
1083 failovermaster = qa_config.AcquireNode(exclude=master)
1085 cmd = ["gnt-cluster", "master-failover"]
1087 AssertCommand(cmd, node=failovermaster)
1088 # Back to original master node
1089 AssertCommand(cmd, node=master)
1091 failovermaster.Release()
1094 def _NodeQueueDrainFile(node):
1095 """Returns path to queue drain file for a node.
1098 return qa_utils.MakeNodePath(node, pathutils.JOB_QUEUE_DRAIN_FILE)
1101 def _AssertDrainFile(node, **kwargs):
1102 """Checks for the queue drain file.
1105 AssertCommand(["test", "-f", _NodeQueueDrainFile(node)], node=node, **kwargs)
1108 def TestClusterMasterFailoverWithDrainedQueue():
1109 """gnt-cluster master-failover with drained queue"""
1110 master = qa_config.GetMasterNode()
1111 failovermaster = qa_config.AcquireNode(exclude=master)
1113 # Ensure queue is not drained
1114 for node in [master, failovermaster]:
1115 _AssertDrainFile(node, fail=True)
1117 # Drain queue on failover master
1118 AssertCommand(["touch", _NodeQueueDrainFile(failovermaster)],
1119 node=failovermaster)
1121 cmd = ["gnt-cluster", "master-failover"]
1123 _AssertDrainFile(failovermaster)
1124 AssertCommand(cmd, node=failovermaster)
1125 _AssertDrainFile(master, fail=True)
1126 _AssertDrainFile(failovermaster, fail=True)
1128 # Back to original master node
1129 AssertCommand(cmd, node=master)
1131 failovermaster.Release()
1133 # Ensure queue is not drained
1134 for node in [master, failovermaster]:
1135 _AssertDrainFile(node, fail=True)
1138 def TestClusterCopyfile():
1139 """gnt-cluster copyfile"""
1140 master = qa_config.GetMasterNode()
1142 uniqueid = utils.NewUUID()
1144 # Create temporary file
1145 f = tempfile.NamedTemporaryFile()
1150 # Upload file to master node
1151 testname = qa_utils.UploadFile(master.primary, f.name)
1153 # Copy file to all nodes
1154 AssertCommand(["gnt-cluster", "copyfile", testname])
1155 _CheckFileOnAllNodes(testname, uniqueid)
1157 _RemoveFileFromAllNodes(testname)
1160 def TestClusterCommand():
1161 """gnt-cluster command"""
1162 uniqueid = utils.NewUUID()
1163 rfile = "/tmp/gnt%s" % utils.NewUUID()
1164 rcmd = utils.ShellQuoteArgs(["echo", "-n", uniqueid])
1165 cmd = utils.ShellQuoteArgs(["gnt-cluster", "command",
1166 "%s >%s" % (rcmd, rfile)])
1170 _CheckFileOnAllNodes(rfile, uniqueid)
1172 _RemoveFileFromAllNodes(rfile)
1175 def TestClusterDestroy():
1176 """gnt-cluster destroy"""
1177 AssertCommand(["gnt-cluster", "destroy", "--yes-do-it"])
1180 def TestClusterRepairDiskSizes():
1181 """gnt-cluster repair-disk-sizes"""
1182 AssertCommand(["gnt-cluster", "repair-disk-sizes"])
1185 def TestSetExclStorCluster(newvalue):
1186 """Set the exclusive_storage node parameter at the cluster level.
1188 @type newvalue: bool
1189 @param newvalue: New value of exclusive_storage
1191 @return: The old value of exclusive_storage
1194 es_path = ["Default node parameters", "exclusive_storage"]
1195 oldvalue = _GetClusterField(es_path)
1196 AssertCommand(["gnt-cluster", "modify", "--node-parameters",
1197 "exclusive_storage=%s" % newvalue])
1198 effvalue = _GetClusterField(es_path)
1199 if effvalue != newvalue:
1200 raise qa_error.Error("exclusive_storage has the wrong value: %s instead"
1201 " of %s" % (effvalue, newvalue))
1202 qa_config.SetExclusiveStorage(newvalue)
1206 def TestExclStorSharedPv(node):
1207 """cluster-verify reports LVs that share the same PV with exclusive_storage.
1210 vgname = qa_config.get("vg-name", constants.DEFAULT_VG)
1211 lvname1 = _QA_LV_PREFIX + "vol1"
1212 lvname2 = _QA_LV_PREFIX + "vol2"
1213 node_name = node.primary
1214 AssertCommand(["lvcreate", "-L1G", "-n", lvname1, vgname], node=node_name)
1215 AssertClusterVerify(fail=True, errors=[constants.CV_ENODEORPHANLV])
1216 AssertCommand(["lvcreate", "-L1G", "-n", lvname2, vgname], node=node_name)
1217 AssertClusterVerify(fail=True, errors=[constants.CV_ENODELVM,
1218 constants.CV_ENODEORPHANLV])
1219 AssertCommand(["lvremove", "-f", "/".join([vgname, lvname1])], node=node_name)
1220 AssertCommand(["lvremove", "-f", "/".join([vgname, lvname2])], node=node_name)
1221 AssertClusterVerify()