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 constants
32 from ganeti import compat
33 from ganeti import utils
34 from ganeti import pathutils
43 from qa_utils import AssertEqual, AssertCommand, GetCommandOutput
46 # Prefix for LVM volumes created by QA code during tests
49 #: cluster verify command
50 _CLUSTER_VERIFY = ["gnt-cluster", "verify"]
53 def _RemoveFileFromAllNodes(filename):
54 """Removes a file from all nodes.
57 for node in qa_config.get("nodes"):
58 AssertCommand(["rm", "-f", filename], node=node)
61 def _CheckFileOnAllNodes(filename, content):
62 """Verifies the content of the given file on all nodes.
65 cmd = utils.ShellQuoteArgs(["cat", filename])
66 for node in qa_config.get("nodes"):
67 AssertEqual(qa_utils.GetCommandOutput(node.primary, cmd), content)
70 def _GetClusterField(field_path):
71 """Get the value of a cluster field.
73 @type field_path: list of strings
74 @param field_path: Names of the groups/fields to navigate to get the desired
75 value, e.g. C{["Default node parameters", "oob_program"]}
76 @return: The effective value of the field (the actual type depends on the
80 assert isinstance(field_path, list)
82 ret = qa_utils.GetObjectInfo(["gnt-cluster", "info"])
83 for key in field_path:
88 # Cluster-verify errors (date, "ERROR", then error code)
89 _CVERROR_RE = re.compile(r"^[\w\s:]+\s+- (ERROR|WARNING):([A-Z0-9_-]+):")
92 def _GetCVErrorCodes(cvout):
95 for l in cvout.splitlines():
96 m = _CVERROR_RE.match(l)
102 elif etype == "WARNING":
107 def _CheckVerifyErrors(actual, expected, etype):
108 exp_codes = compat.UniqueFrozenset(e for (_, e, _) in expected)
109 if not actual.issuperset(exp_codes):
110 missing = exp_codes.difference(actual)
111 raise qa_error.Error("Cluster-verify didn't return these expected"
112 " %ss: %s" % (etype, utils.CommaJoin(missing)))
115 def AssertClusterVerify(fail=False, errors=None, warnings=None):
116 """Run cluster-verify and check the result
119 @param fail: if cluster-verify is expected to fail instead of succeeding
120 @type errors: list of tuples
121 @param errors: List of CV_XXX errors that are expected; if specified, all the
122 errors listed must appear in cluster-verify output. A non-empty value
123 implies C{fail=True}.
124 @type warnings: list of tuples
125 @param warnings: Same as C{errors} but for warnings.
128 cvcmd = "gnt-cluster verify"
129 mnode = qa_config.GetMasterNode()
130 if errors or warnings:
131 cvout = GetCommandOutput(mnode.primary, cvcmd + " --error-codes",
132 fail=(fail or errors))
133 (act_errs, act_warns) = _GetCVErrorCodes(cvout)
135 _CheckVerifyErrors(act_errs, errors, "error")
137 _CheckVerifyErrors(act_warns, warnings, "warning")
139 AssertCommand(cvcmd, fail=fail, node=mnode)
142 # data for testing failures due to bad keys/values for disk parameters
143 _FAIL_PARAMS = ["nonexistent:resync-rate=1",
144 "drbd:nonexistent=1",
145 "drbd:resync-rate=invalid",
149 def TestClusterInitDisk():
150 """gnt-cluster init -D"""
151 name = qa_config.get("name")
152 for param in _FAIL_PARAMS:
153 AssertCommand(["gnt-cluster", "init", "-D", param, name], fail=True)
156 def TestClusterInit(rapi_user, rapi_secret):
157 """gnt-cluster init"""
158 master = qa_config.GetMasterNode()
160 rapi_users_path = qa_utils.MakeNodePath(master, pathutils.RAPI_USERS_FILE)
161 rapi_dir = os.path.dirname(rapi_users_path)
163 # First create the RAPI credentials
164 fh = tempfile.NamedTemporaryFile()
166 fh.write("%s %s write\n" % (rapi_user, rapi_secret))
169 tmpru = qa_utils.UploadFile(master.primary, fh.name)
171 AssertCommand(["mkdir", "-p", rapi_dir])
172 AssertCommand(["mv", tmpru, rapi_users_path])
174 AssertCommand(["rm", "-f", tmpru])
179 enabled_disk_templates = qa_config.GetEnabledDiskTemplates()
181 "gnt-cluster", "init",
182 "--primary-ip-version=%d" % qa_config.get("primary_ip_version", 4),
183 "--enabled-hypervisors=%s" % ",".join(qa_config.GetEnabledHypervisors()),
184 "--enabled-disk-templates=%s" %
185 ",".join(enabled_disk_templates),
187 if constants.DT_FILE in enabled_disk_templates:
189 "--file-storage-dir=%s" %
190 qa_config.get("default-file-storage-dir",
191 pathutils.DEFAULT_FILE_STORAGE_DIR))
193 for spec_type in ("mem-size", "disk-size", "disk-count", "cpu-count",
195 for spec_val in ("min", "max", "std"):
196 spec = qa_config.get("ispec_%s_%s" %
197 (spec_type.replace("-", "_"), spec_val), None)
199 cmd.append("--specs-%s=%s=%d" % (spec_type, spec_val, spec))
202 cmd.append("--secondary-ip=%s" % master.secondary)
204 if utils.IsLvmEnabled(qa_config.GetEnabledDiskTemplates()):
205 vgname = qa_config.get("vg-name", constants.DEFAULT_VG)
207 cmd.append("--vg-name=%s" % vgname)
209 raise qa_error.Error("Please specify a volume group if you enable"
210 " lvm-based disk templates in the QA.")
212 master_netdev = qa_config.get("master-netdev", None)
214 cmd.append("--master-netdev=%s" % master_netdev)
216 nicparams = qa_config.get("default-nicparams", None)
218 cmd.append("--nic-parameters=%s" %
219 ",".join(utils.FormatKeyValue(nicparams)))
221 # Cluster value of the exclusive-storage node parameter
222 e_s = qa_config.get("exclusive-storage")
224 cmd.extend(["--node-parameters", "exclusive_storage=%s" % e_s])
227 qa_config.SetExclusiveStorage(e_s)
229 extra_args = qa_config.get("cluster-init-args")
232 # This option was removed in 2.10, but in order to not break QA of older
233 # branches we remove it from the extra_args if it is in there.
234 opt_drbd_storage = "--no-drbd-storage"
235 if opt_drbd_storage in extra_args:
236 extra_args.remove(opt_drbd_storage)
237 cmd.extend(extra_args)
239 cmd.append(qa_config.get("name"))
243 cmd = ["gnt-cluster", "modify"]
245 # hypervisor parameter modifications
246 hvp = qa_config.get("hypervisor-parameters", {})
247 for k, v in hvp.items():
248 cmd.extend(["-H", "%s:%s" % (k, v)])
249 # backend parameter modifications
250 bep = qa_config.get("backend-parameters", "")
252 cmd.extend(["-B", bep])
258 osp = qa_config.get("os-parameters", {})
259 for k, v in osp.items():
260 AssertCommand(["gnt-os", "modify", "-O", v, k])
262 # OS hypervisor parameters
263 os_hvp = qa_config.get("os-hvp", {})
264 for os_name in os_hvp:
265 for hv, hvp in os_hvp[os_name].items():
266 AssertCommand(["gnt-os", "modify", "-H", "%s:%s" % (hv, hvp), os_name])
269 def TestClusterRename():
270 """gnt-cluster rename"""
271 cmd = ["gnt-cluster", "rename", "-f"]
273 original_name = qa_config.get("name")
274 rename_target = qa_config.get("rename", None)
275 if rename_target is None:
276 print qa_logging.FormatError('"rename" entry is missing')
280 cmd + [rename_target],
282 cmd + [original_name],
288 def TestClusterOob():
289 """out-of-band framework"""
290 oob_path_exists = "/tmp/ganeti-qa-oob-does-exist-%s" % utils.NewUUID()
292 AssertCommand(_CLUSTER_VERIFY)
293 AssertCommand(["gnt-cluster", "modify", "--node-parameters",
294 "oob_program=/tmp/ganeti-qa-oob-does-not-exist-%s" %
297 AssertCommand(_CLUSTER_VERIFY, fail=True)
299 AssertCommand(["touch", oob_path_exists])
300 AssertCommand(["chmod", "0400", oob_path_exists])
301 AssertCommand(["gnt-cluster", "copyfile", oob_path_exists])
304 AssertCommand(["gnt-cluster", "modify", "--node-parameters",
305 "oob_program=%s" % oob_path_exists])
307 AssertCommand(_CLUSTER_VERIFY, fail=True)
309 AssertCommand(["chmod", "0500", oob_path_exists])
310 AssertCommand(["gnt-cluster", "copyfile", oob_path_exists])
312 AssertCommand(_CLUSTER_VERIFY)
314 AssertCommand(["gnt-cluster", "command", "rm", oob_path_exists])
316 AssertCommand(["gnt-cluster", "modify", "--node-parameters",
320 def TestClusterEpo():
321 """gnt-cluster epo"""
322 master = qa_config.GetMasterNode()
324 # Assert that OOB is unavailable for all nodes
325 result_output = GetCommandOutput(master.primary,
326 "gnt-node list --verbose --no-headers -o"
328 AssertEqual(compat.all(powered == "(unavail)"
329 for powered in result_output.splitlines()), True)
332 AssertCommand(["gnt-cluster", "epo", "--groups", "--all"], fail=True)
333 # --all doesn't expect arguments
334 AssertCommand(["gnt-cluster", "epo", "--all", "some_arg"], fail=True)
336 # Unless --all is given master is not allowed to be in the list
337 AssertCommand(["gnt-cluster", "epo", "-f", master.primary], fail=True)
339 # This shouldn't fail
340 AssertCommand(["gnt-cluster", "epo", "-f", "--all"])
342 # All instances should have been stopped now
343 result_output = GetCommandOutput(master.primary,
344 "gnt-instance list --no-headers -o status")
345 # ERROR_down because the instance is stopped but not recorded as such
346 AssertEqual(compat.all(status == "ERROR_down"
347 for status in result_output.splitlines()), True)
349 # Now start everything again
350 AssertCommand(["gnt-cluster", "epo", "--on", "-f", "--all"])
352 # All instances should have been started now
353 result_output = GetCommandOutput(master.primary,
354 "gnt-instance list --no-headers -o status")
355 AssertEqual(compat.all(status == "running"
356 for status in result_output.splitlines()), True)
359 def TestClusterVerify():
360 """gnt-cluster verify"""
361 AssertCommand(_CLUSTER_VERIFY)
362 AssertCommand(["gnt-cluster", "verify-disks"])
365 def TestClusterVerifyDisksBrokenDRBD(instance, inst_nodes):
366 """gnt-cluster verify-disks with broken DRBD"""
367 qa_daemon.TestPauseWatcher()
370 info = qa_instance.GetInstanceInfo(instance.name)
371 snode = inst_nodes[1]
372 for idx, minor in enumerate(info["drbd-minors"][snode.primary]):
375 "(drbdsetup %d down >/dev/null 2>&1;" \
376 " drbdsetup down resource%d >/dev/null 2>&1) || /bin/true" % \
380 "(drbdsetup %d detach >/dev/null 2>&1;" \
381 " drbdsetup detach %d >/dev/null 2>&1) || /bin/true" % \
383 AssertCommand(break_drbd_cmd, node=snode)
385 verify_output = GetCommandOutput(qa_config.GetMasterNode().primary,
386 "gnt-cluster verify-disks")
387 activation_msg = "Activating disks for instance '%s'" % instance.name
388 if activation_msg not in verify_output:
389 raise qa_error.Error("gnt-cluster verify-disks did not activate broken"
390 " DRBD disks:\n%s" % verify_output)
392 verify_output = GetCommandOutput(qa_config.GetMasterNode().primary,
393 "gnt-cluster verify-disks")
394 if activation_msg in verify_output:
395 raise qa_error.Error("gnt-cluster verify-disks wants to activate broken"
396 " DRBD disks on second attempt:\n%s" % verify_output)
398 AssertCommand(_CLUSTER_VERIFY)
400 qa_daemon.TestResumeWatcher()
404 """gnt-debug test-jobqueue"""
405 AssertCommand(["gnt-debug", "test-jobqueue"])
409 """gnt-debug delay"""
410 AssertCommand(["gnt-debug", "delay", "1"])
411 AssertCommand(["gnt-debug", "delay", "--no-master", "1"])
412 AssertCommand(["gnt-debug", "delay", "--no-master",
413 "-n", node.primary, "1"])
416 def TestClusterReservedLvs():
417 """gnt-cluster reserved lvs"""
418 # if no lvm-based templates are supported, skip the test
419 if not qa_config.IsStorageTypeSupported(constants.ST_LVM_VG):
421 vgname = qa_config.get("vg-name", constants.DEFAULT_VG)
422 lvname = _QA_LV_PREFIX + "test"
423 lvfullname = "/".join([vgname, lvname])
425 (False, _CLUSTER_VERIFY),
426 (False, ["gnt-cluster", "modify", "--reserved-lvs", ""]),
427 (False, ["lvcreate", "-L1G", "-n", lvname, vgname]),
428 (True, _CLUSTER_VERIFY),
429 (False, ["gnt-cluster", "modify", "--reserved-lvs",
430 "%s,.*/other-test" % lvfullname]),
431 (False, _CLUSTER_VERIFY),
432 (False, ["gnt-cluster", "modify", "--reserved-lvs",
433 ".*/%s.*" % _QA_LV_PREFIX]),
434 (False, _CLUSTER_VERIFY),
435 (False, ["gnt-cluster", "modify", "--reserved-lvs", ""]),
436 (True, _CLUSTER_VERIFY),
437 (False, ["lvremove", "-f", lvfullname]),
438 (False, _CLUSTER_VERIFY),
440 AssertCommand(cmd, fail=fail)
443 def TestClusterModifyEmpty():
444 """gnt-cluster modify"""
445 AssertCommand(["gnt-cluster", "modify"], fail=True)
448 def TestClusterModifyDisk():
449 """gnt-cluster modify -D"""
450 for param in _FAIL_PARAMS:
451 AssertCommand(["gnt-cluster", "modify", "-D", param], fail=True)
454 def _GetOtherEnabledDiskTemplate(undesired_disk_templates,
455 enabled_disk_templates):
456 """Returns one template that is not in the undesired set.
458 @type undesired_disk_templates: list of string
459 @param undesired_disk_templates: a list of disk templates that we want to
460 exclude when drawing one disk template from the list of enabled
462 @type enabled_disk_templates: list of string
463 @param enabled_disk_templates: list of enabled disk templates (in QA)
466 desired_templates = list(set(enabled_disk_templates)
467 - set(undesired_disk_templates))
468 if desired_templates:
469 template = desired_templates[0]
471 # If no desired disk template is available for QA, choose 'diskless' and
473 template = constants.ST_DISKLESS
478 def TestClusterModifyFileBasedStorageDir(
479 file_disk_template, dir_config_key, default_dir, option_name):
480 """Tests gnt-cluster modify wrt to file-based directory options.
482 @type file_disk_template: string
483 @param file_disk_template: file-based disk template
484 @type dir_config_key: string
485 @param dir_config_key: key for the QA config to retrieve the default
487 @type default_dir: string
488 @param default_dir: default directory, if the QA config does not specify
490 @type option_name: string
491 @param option_name: name of the option of 'gnt-cluster modify' to
495 enabled_disk_templates = qa_config.GetEnabledDiskTemplates()
496 assert file_disk_template in [constants.DT_FILE, constants.DT_SHARED_FILE]
497 if not qa_config.IsTemplateSupported(file_disk_template):
500 # Get some non-file-based disk template to disable file storage
501 other_disk_template = _GetOtherEnabledDiskTemplate(
502 utils.storage.GetDiskTemplatesOfStorageType(constants.ST_FILE),
503 enabled_disk_templates)
505 file_storage_dir = qa_config.get(dir_config_key, default_dir)
506 invalid_file_storage_dir = "/boot/"
509 (False, ["gnt-cluster", "modify",
510 "--enabled-disk-templates=%s" % file_disk_template,
511 "--ipolicy-disk-templates=%s" % file_disk_template]),
512 (False, ["gnt-cluster", "modify",
513 "--%s=%s" % (option_name, file_storage_dir)]),
514 (False, ["gnt-cluster", "modify",
515 "--%s=%s" % (option_name, invalid_file_storage_dir)]),
516 # file storage dir is set to an inacceptable path, thus verify
518 (True, ["gnt-cluster", "verify"]),
519 # unsetting the storage dir while file storage is enabled
521 (True, ["gnt-cluster", "modify",
522 "--%s=" % option_name]),
523 (False, ["gnt-cluster", "modify",
524 "--%s=%s" % (option_name, file_storage_dir)]),
525 (False, ["gnt-cluster", "modify",
526 "--enabled-disk-templates=%s" % other_disk_template,
527 "--ipolicy-disk-templates=%s" % other_disk_template]),
528 (False, ["gnt-cluster", "modify",
529 "--%s=%s" % (option_name, invalid_file_storage_dir)]),
530 # file storage is set to an inacceptable path, but file storage
531 # is disabled, thus verify should not fail
532 (False, ["gnt-cluster", "verify"]),
533 # unsetting the file storage dir while file storage is not enabled
535 (False, ["gnt-cluster", "modify",
536 "--%s=" % option_name]),
537 # resetting everything to sane values
538 (False, ["gnt-cluster", "modify",
539 "--%s=%s" % (option_name, file_storage_dir),
540 "--enabled-disk-templates=%s" % ",".join(enabled_disk_templates),
541 "--ipolicy-disk-templates=%s" % ",".join(enabled_disk_templates)])
543 AssertCommand(cmd, fail=fail)
546 def TestClusterModifyFileStorageDir():
547 """gnt-cluster modify --file-storage-dir=..."""
548 TestClusterModifyFileBasedStorageDir(
549 constants.DT_FILE, "default-file-storage-dir",
550 pathutils.DEFAULT_FILE_STORAGE_DIR,
554 def TestClusterModifySharedFileStorageDir():
555 """gnt-cluster modify --shared-file-storage-dir=..."""
556 TestClusterModifyFileBasedStorageDir(
557 constants.DT_SHARED_FILE, "default-shared-file-storage-dir",
558 pathutils.DEFAULT_SHARED_FILE_STORAGE_DIR,
559 "shared-file-storage-dir")
562 def TestClusterModifyDiskTemplates():
563 """gnt-cluster modify --enabled-disk-templates=..."""
564 enabled_disk_templates = qa_config.GetEnabledDiskTemplates()
565 default_disk_template = qa_config.GetDefaultDiskTemplate()
567 _TestClusterModifyDiskTemplatesArguments(default_disk_template)
568 _TestClusterModifyDiskTemplatesDrbdHelper(enabled_disk_templates)
569 _TestClusterModifyDiskTemplatesVgName(enabled_disk_templates)
571 _RestoreEnabledDiskTemplates()
572 nodes = qa_config.AcquireManyNodes(2)
574 instance_template = enabled_disk_templates[0]
575 instance = qa_instance.CreateInstanceByDiskTemplate(nodes, instance_template)
577 _TestClusterModifyUsedDiskTemplate(instance_template,
578 enabled_disk_templates)
580 qa_instance.TestInstanceRemove(instance)
581 _RestoreEnabledDiskTemplates()
584 def _RestoreEnabledDiskTemplates():
585 """Sets the list of enabled disk templates back to the list of enabled disk
586 templates from the QA configuration. This can be used to make sure that
587 the tests that modify the list of disk templates do not interfere with
591 enabled_disk_templates = qa_config.GetEnabledDiskTemplates()
592 cmd = ["gnt-cluster", "modify",
593 "--enabled-disk-templates=%s" % ",".join(enabled_disk_templates),
594 "--ipolicy-disk-templates=%s" % ",".join(enabled_disk_templates),
597 if utils.IsLvmEnabled(qa_config.GetEnabledDiskTemplates()):
598 vgname = qa_config.get("vg-name", constants.DEFAULT_VG)
599 cmd.append("--vg-name=%s" % vgname)
601 AssertCommand(cmd, fail=False)
604 def _TestClusterModifyDiskTemplatesDrbdHelper(enabled_disk_templates):
605 """Tests argument handling of 'gnt-cluster modify' with respect to
606 the parameter '--drbd-usermode-helper'. This test is independent
610 _RestoreEnabledDiskTemplates()
612 if constants.DT_DRBD8 not in enabled_disk_templates:
614 if constants.DT_PLAIN not in enabled_disk_templates:
617 drbd_usermode_helper = qa_config.get("drbd-usermode-helper", "/bin/true")
618 bogus_usermode_helper = "/tmp/pinkbunny"
619 for command, fail in [
620 (["gnt-cluster", "modify",
621 "--enabled-disk-templates=%s" % constants.DT_DRBD8,
622 "--ipolicy-disk-templates=%s" % constants.DT_DRBD8], False),
623 (["gnt-cluster", "modify",
624 "--drbd-usermode-helper=%s" % drbd_usermode_helper], False),
625 (["gnt-cluster", "modify",
626 "--drbd-usermode-helper=%s" % bogus_usermode_helper], True),
627 # unsetting helper when DRBD is enabled should not work
628 (["gnt-cluster", "modify",
629 "--drbd-usermode-helper="], True),
630 (["gnt-cluster", "modify",
631 "--enabled-disk-templates=%s" % constants.DT_PLAIN,
632 "--ipolicy-disk-templates=%s" % constants.DT_PLAIN], False),
633 (["gnt-cluster", "modify",
634 "--drbd-usermode-helper="], False),
635 (["gnt-cluster", "modify",
636 "--drbd-usermode-helper=%s" % drbd_usermode_helper], False),
637 (["gnt-cluster", "modify",
638 "--drbd-usermode-helper=%s" % drbd_usermode_helper,
639 "--enabled-disk-templates=%s" % constants.DT_DRBD8,
640 "--ipolicy-disk-templates=%s" % constants.DT_DRBD8], False),
641 (["gnt-cluster", "modify",
642 "--drbd-usermode-helper=",
643 "--enabled-disk-templates=%s" % constants.DT_PLAIN,
644 "--ipolicy-disk-templates=%s" % constants.DT_PLAIN], False),
645 (["gnt-cluster", "modify",
646 "--drbd-usermode-helper=%s" % drbd_usermode_helper,
647 "--enabled-disk-templates=%s" % constants.DT_DRBD8,
648 "--ipolicy-disk-templates=%s" % constants.DT_DRBD8], False),
650 AssertCommand(command, fail=fail)
651 _RestoreEnabledDiskTemplates()
654 def _TestClusterModifyDiskTemplatesArguments(default_disk_template):
655 """Tests argument handling of 'gnt-cluster modify' with respect to
656 the parameter '--enabled-disk-templates'. This test is independent
660 _RestoreEnabledDiskTemplates()
663 AssertCommand(["gnt-cluster", "modify",
664 "--enabled-disk-templates=pinkbunny"],
667 # duplicate entries do no harm
669 ["gnt-cluster", "modify",
670 "--enabled-disk-templates=%s,%s" %
671 (default_disk_template, default_disk_template),
672 "--ipolicy-disk-templates=%s" % default_disk_template],
676 def _TestClusterModifyDiskTemplatesVgName(enabled_disk_templates):
677 """Tests argument handling of 'gnt-cluster modify' with respect to
678 the parameter '--enabled-disk-templates' and '--vg-name'. This test is
679 independent of instances.
682 if not utils.IsLvmEnabled(enabled_disk_templates):
683 # These tests only make sense if lvm is enabled for QA
686 # determine an LVM and a non-LVM disk template for the tests
687 non_lvm_template = _GetOtherEnabledDiskTemplate(constants.DTS_LVM,
688 enabled_disk_templates)
689 lvm_template = list(set(enabled_disk_templates) & constants.DTS_LVM)[0]
691 vgname = qa_config.get("vg-name", constants.DEFAULT_VG)
693 # Clean start: unset volume group name, disable lvm storage
695 ["gnt-cluster", "modify",
696 "--enabled-disk-templates=%s" % non_lvm_template,
697 "--ipolicy-disk-templates=%s" % non_lvm_template,
701 # Try to enable lvm, when no volume group is given
703 ["gnt-cluster", "modify",
704 "--enabled-disk-templates=%s" % lvm_template,
705 "--ipolicy-disk-templates=%s" % lvm_template],
708 # Set volume group, with lvm still disabled: just a warning
709 AssertCommand(["gnt-cluster", "modify", "--vg-name=%s" % vgname], fail=False)
711 # Try unsetting vg name and enabling lvm at the same time
713 ["gnt-cluster", "modify",
714 "--enabled-disk-templates=%s" % lvm_template,
715 "--ipolicy-disk-templates=%s" % lvm_template,
719 # Enable lvm with vg name present
721 ["gnt-cluster", "modify",
722 "--enabled-disk-templates=%s" % lvm_template,
723 "--ipolicy-disk-templates=%s" % lvm_template],
726 # Try unsetting vg name with lvm still enabled
727 AssertCommand(["gnt-cluster", "modify", "--vg-name="], fail=True)
729 # Disable lvm with vg name still set
731 ["gnt-cluster", "modify",
732 "--enabled-disk-templates=%s" % non_lvm_template,
733 "--ipolicy-disk-templates=%s" % non_lvm_template,
737 # Try unsetting vg name with lvm disabled
738 AssertCommand(["gnt-cluster", "modify", "--vg-name="], fail=False)
740 # Set vg name and enable lvm at the same time
742 ["gnt-cluster", "modify",
743 "--enabled-disk-templates=%s" % lvm_template,
744 "--ipolicy-disk-templates=%s" % lvm_template,
745 "--vg-name=%s" % vgname],
748 # Unset vg name and disable lvm at the same time
750 ["gnt-cluster", "modify",
751 "--enabled-disk-templates=%s" % non_lvm_template,
752 "--ipolicy-disk-templates=%s" % non_lvm_template,
756 _RestoreEnabledDiskTemplates()
759 def _TestClusterModifyUsedDiskTemplate(instance_template,
760 enabled_disk_templates):
761 """Tests that disk templates that are currently in use by instances cannot
762 be disabled on the cluster.
765 # If the list of enabled disk templates contains only one template
766 # we need to add some other templates, because the list of enabled disk
767 # templates can only be set to a non-empty list.
768 new_disk_templates = list(set(enabled_disk_templates)
769 - set([instance_template]))
770 if not new_disk_templates:
771 new_disk_templates = list(set([constants.DT_DISKLESS, constants.DT_BLOCK])
772 - set([instance_template]))
774 ["gnt-cluster", "modify",
775 "--enabled-disk-templates=%s" % ",".join(new_disk_templates),
776 "--ipolicy-disk-templates=%s" % ",".join(new_disk_templates)],
780 def TestClusterModifyBe():
781 """gnt-cluster modify -B"""
784 (False, ["gnt-cluster", "modify", "-B", "maxmem=256"]),
785 (False, ["sh", "-c", "gnt-cluster info|grep '^ *maxmem: 256$'"]),
786 (False, ["gnt-cluster", "modify", "-B", "minmem=256"]),
787 (False, ["sh", "-c", "gnt-cluster info|grep '^ *minmem: 256$'"]),
788 (True, ["gnt-cluster", "modify", "-B", "maxmem=a"]),
789 (False, ["sh", "-c", "gnt-cluster info|grep '^ *maxmem: 256$'"]),
790 (True, ["gnt-cluster", "modify", "-B", "minmem=a"]),
791 (False, ["sh", "-c", "gnt-cluster info|grep '^ *minmem: 256$'"]),
792 (False, ["gnt-cluster", "modify", "-B", "maxmem=128,minmem=128"]),
793 (False, ["sh", "-c", "gnt-cluster info|grep '^ *maxmem: 128$'"]),
794 (False, ["sh", "-c", "gnt-cluster info|grep '^ *minmem: 128$'"]),
796 (False, ["gnt-cluster", "modify", "-B", "vcpus=4"]),
797 (False, ["sh", "-c", "gnt-cluster info|grep '^ *vcpus: 4$'"]),
798 (True, ["gnt-cluster", "modify", "-B", "vcpus=a"]),
799 (False, ["gnt-cluster", "modify", "-B", "vcpus=1"]),
800 (False, ["sh", "-c", "gnt-cluster info|grep '^ *vcpus: 1$'"]),
802 (False, ["gnt-cluster", "modify", "-B", "auto_balance=False"]),
803 (False, ["sh", "-c", "gnt-cluster info|grep '^ *auto_balance: False$'"]),
804 (True, ["gnt-cluster", "modify", "-B", "auto_balance=1"]),
805 (False, ["gnt-cluster", "modify", "-B", "auto_balance=True"]),
806 (False, ["sh", "-c", "gnt-cluster info|grep '^ *auto_balance: True$'"]),
808 AssertCommand(cmd, fail=fail)
810 # redo the original-requested BE parameters, if any
811 bep = qa_config.get("backend-parameters", "")
813 AssertCommand(["gnt-cluster", "modify", "-B", bep])
816 def _GetClusterIPolicy():
817 """Return the run-time values of the cluster-level instance policy.
820 @return: (policy, specs), where:
821 - policy is a dictionary of the policy values, instance specs excluded
822 - specs is a dictionary containing only the specs, using the internal
823 format (see L{constants.IPOLICY_DEFAULTS} for an example)
826 info = qa_utils.GetObjectInfo(["gnt-cluster", "info"])
827 policy = info["Instance policy - limits for instances"]
828 (ret_policy, ret_specs) = qa_utils.ParseIPolicy(policy)
831 assert "minmax" in ret_specs and "std" in ret_specs
832 assert len(ret_specs["minmax"]) > 0
833 assert len(ret_policy) > 0
834 return (ret_policy, ret_specs)
837 def TestClusterModifyIPolicy():
838 """gnt-cluster modify --ipolicy-*"""
839 basecmd = ["gnt-cluster", "modify"]
840 (old_policy, old_specs) = _GetClusterIPolicy()
841 for par in ["vcpu-ratio", "spindle-ratio"]:
842 curr_val = float(old_policy[par])
848 # Restore the old value
851 for (good, val) in test_values:
852 cmd = basecmd + ["--ipolicy-%s=%s" % (par, val)]
853 AssertCommand(cmd, fail=not good)
856 # Check the affected parameter
857 (eff_policy, eff_specs) = _GetClusterIPolicy()
858 AssertEqual(float(eff_policy[par]), curr_val)
859 # Check everything else
860 AssertEqual(eff_specs, old_specs)
861 for p in eff_policy.keys():
864 AssertEqual(eff_policy[p], old_policy[p])
866 # Allowing disk templates via ipolicy requires them to be
867 # enabled on the cluster.
868 if not (qa_config.IsTemplateSupported(constants.DT_PLAIN)
869 and qa_config.IsTemplateSupported(constants.DT_DRBD8)):
871 # Disk templates are treated slightly differently
872 par = "disk-templates"
873 disp_str = "allowed disk templates"
874 curr_val = old_policy[disp_str]
876 (True, constants.DT_PLAIN),
877 (True, "%s,%s" % (constants.DT_PLAIN, constants.DT_DRBD8)),
878 (False, "thisisnotadisktemplate"),
880 # Restore the old value
881 (True, curr_val.replace(" ", "")),
883 for (good, val) in test_values:
884 cmd = basecmd + ["--ipolicy-%s=%s" % (par, val)]
885 AssertCommand(cmd, fail=not good)
888 # Check the affected parameter
889 (eff_policy, eff_specs) = _GetClusterIPolicy()
890 AssertEqual(eff_policy[disp_str].replace(" ", ""), curr_val)
891 # Check everything else
892 AssertEqual(eff_specs, old_specs)
893 for p in eff_policy.keys():
896 AssertEqual(eff_policy[p], old_policy[p])
899 def TestClusterSetISpecs(new_specs=None, diff_specs=None, fail=False,
901 """Change instance specs.
903 At most one of new_specs or diff_specs can be specified.
905 @type new_specs: dict
906 @param new_specs: new complete specs, in the same format returned by
907 L{_GetClusterIPolicy}
908 @type diff_specs: dict
909 @param diff_specs: partial specs, it can be an incomplete specifications, but
910 if min/max specs are specified, their number must match the number of the
913 @param fail: if the change is expected to fail
914 @type old_values: tuple
915 @param old_values: (old_policy, old_specs), as returned by
916 L{_GetClusterIPolicy}
917 @return: same as L{_GetClusterIPolicy}
920 build_cmd = lambda opts: ["gnt-cluster", "modify"] + opts
921 return qa_utils.TestSetISpecs(
922 new_specs=new_specs, diff_specs=diff_specs,
923 get_policy_fn=_GetClusterIPolicy, build_cmd_fn=build_cmd,
924 fail=fail, old_values=old_values)
927 def TestClusterModifyISpecs():
928 """gnt-cluster modify --specs-*"""
929 params = ["memory-size", "disk-size", "disk-count", "cpu-count", "nic-count"]
930 (cur_policy, cur_specs) = _GetClusterIPolicy()
931 # This test assumes that there is only one min/max bound
932 assert len(cur_specs[constants.ISPECS_MINMAX]) == 1
947 # This is to restore the old values
949 cur_specs[constants.ISPECS_MINMAX][0][constants.ISPECS_MIN][par],
950 cur_specs[constants.ISPECS_STD][par],
951 cur_specs[constants.ISPECS_MINMAX][0][constants.ISPECS_MAX][par])
953 for (good, mn, st, mx) in test_values:
955 constants.ISPECS_MINMAX: [{
956 constants.ISPECS_MIN: {par: mn},
957 constants.ISPECS_MAX: {par: mx}
959 constants.ISPECS_STD: {par: st}
961 cur_state = (cur_policy, cur_specs)
962 # We update cur_specs, as we've copied the values to restore already
963 (cur_policy, cur_specs) = TestClusterSetISpecs(
964 diff_specs=new_vals, fail=not good, old_values=cur_state)
966 # Get the ipolicy command
967 mnode = qa_config.GetMasterNode()
968 initcmd = GetCommandOutput(mnode.primary, "gnt-cluster show-ispecs-cmd")
969 modcmd = ["gnt-cluster", "modify"]
970 opts = initcmd.split()
971 assert opts[0:2] == ["gnt-cluster", "init"]
972 for k in range(2, len(opts) - 1):
973 if opts[k].startswith("--ipolicy-"):
974 assert k + 2 <= len(opts)
975 modcmd.extend(opts[k:k + 2])
976 # Re-apply the ipolicy (this should be a no-op)
977 AssertCommand(modcmd)
978 new_initcmd = GetCommandOutput(mnode.primary, "gnt-cluster show-ispecs-cmd")
979 AssertEqual(initcmd, new_initcmd)
982 def TestClusterInfo():
983 """gnt-cluster info"""
984 AssertCommand(["gnt-cluster", "info"])
987 def TestClusterRedistConf():
988 """gnt-cluster redist-conf"""
989 AssertCommand(["gnt-cluster", "redist-conf"])
992 def TestClusterGetmaster():
993 """gnt-cluster getmaster"""
994 AssertCommand(["gnt-cluster", "getmaster"])
997 def TestClusterVersion():
998 """gnt-cluster version"""
999 AssertCommand(["gnt-cluster", "version"])
1002 def TestClusterRenewCrypto():
1003 """gnt-cluster renew-crypto"""
1004 master = qa_config.GetMasterNode()
1006 # Conflicting options
1007 cmd = ["gnt-cluster", "renew-crypto", "--force",
1008 "--new-cluster-certificate", "--new-confd-hmac-key"]
1010 ["--new-rapi-certificate", "--rapi-certificate=/dev/null"],
1011 ["--new-cluster-domain-secret", "--cluster-domain-secret=/dev/null"],
1013 for i in conflicting:
1014 AssertCommand(cmd + i, fail=True)
1016 # Invalid RAPI certificate
1017 cmd = ["gnt-cluster", "renew-crypto", "--force",
1018 "--rapi-certificate=/dev/null"]
1019 AssertCommand(cmd, fail=True)
1021 rapi_cert_backup = qa_utils.BackupFile(master.primary,
1022 pathutils.RAPI_CERT_FILE)
1024 # Custom RAPI certificate
1025 fh = tempfile.NamedTemporaryFile()
1027 # Ensure certificate doesn't cause "gnt-cluster verify" to complain
1028 validity = constants.SSL_CERT_EXPIRATION_WARN * 3
1030 utils.GenerateSelfSignedSslCert(fh.name, validity=validity)
1032 tmpcert = qa_utils.UploadFile(master.primary, fh.name)
1034 AssertCommand(["gnt-cluster", "renew-crypto", "--force",
1035 "--rapi-certificate=%s" % tmpcert])
1037 AssertCommand(["rm", "-f", tmpcert])
1039 # Custom cluster domain secret
1040 cds_fh = tempfile.NamedTemporaryFile()
1041 cds_fh.write(utils.GenerateSecret())
1045 tmpcds = qa_utils.UploadFile(master.primary, cds_fh.name)
1047 AssertCommand(["gnt-cluster", "renew-crypto", "--force",
1048 "--cluster-domain-secret=%s" % tmpcds])
1050 AssertCommand(["rm", "-f", tmpcds])
1053 AssertCommand(["gnt-cluster", "renew-crypto", "--force",
1054 "--new-cluster-certificate", "--new-confd-hmac-key",
1055 "--new-rapi-certificate", "--new-cluster-domain-secret"])
1057 # Restore RAPI certificate
1058 AssertCommand(["gnt-cluster", "renew-crypto", "--force",
1059 "--rapi-certificate=%s" % rapi_cert_backup])
1061 AssertCommand(["rm", "-f", rapi_cert_backup])
1064 def TestClusterBurnin():
1066 master = qa_config.GetMasterNode()
1068 options = qa_config.get("options", {})
1069 disk_template = options.get("burnin-disk-template", constants.DT_DRBD8)
1070 parallel = options.get("burnin-in-parallel", False)
1071 check_inst = options.get("burnin-check-instances", False)
1072 do_rename = options.get("burnin-rename", "")
1073 do_reboot = options.get("burnin-reboot", True)
1074 reboot_types = options.get("reboot-types", constants.REBOOT_TYPES)
1076 # Get as many instances as we need
1080 num = qa_config.get("options", {}).get("burnin-instances", 1)
1081 for _ in range(0, num):
1082 instances.append(qa_config.AcquireInstance())
1083 except qa_error.OutOfInstancesError:
1084 print "Not enough instances, continuing anyway."
1086 if len(instances) < 1:
1087 raise qa_error.Error("Burnin needs at least one instance")
1089 script = qa_utils.UploadFile(master.primary, "../tools/burnin")
1091 disks = qa_config.GetDiskOptions()
1094 "PYTHONPATH=%s" % _constants.VERSIONEDSHAREDIR,
1096 "--os=%s" % qa_config.get("os"),
1097 "--minmem-size=%s" % qa_config.get(constants.BE_MINMEM),
1098 "--maxmem-size=%s" % qa_config.get(constants.BE_MAXMEM),
1099 "--disk-size=%s" % ",".join([d.get("size") for d in disks]),
1100 "--disk-growth=%s" % ",".join([d.get("growth") for d in disks]),
1101 "--disk-template=%s" % disk_template]
1103 cmd.append("--parallel")
1104 cmd.append("--early-release")
1106 cmd.append("--http-check")
1108 cmd.append("--rename=%s" % do_rename)
1110 cmd.append("--no-reboot")
1112 cmd.append("--reboot-types=%s" % ",".join(reboot_types))
1113 cmd += [inst.name for inst in instances]
1116 AssertCommand(["rm", "-f", script])
1119 for inst in instances:
1123 def TestClusterMasterFailover():
1124 """gnt-cluster master-failover"""
1125 master = qa_config.GetMasterNode()
1126 failovermaster = qa_config.AcquireNode(exclude=master)
1128 cmd = ["gnt-cluster", "master-failover"]
1129 node_list_cmd = ["gnt-node", "list"]
1131 AssertCommand(cmd, node=failovermaster)
1132 AssertCommand(node_list_cmd, node=failovermaster)
1133 # Back to original master node
1134 AssertCommand(cmd, node=master)
1135 AssertCommand(node_list_cmd, node=master)
1137 failovermaster.Release()
1140 def _NodeQueueDrainFile(node):
1141 """Returns path to queue drain file for a node.
1144 return qa_utils.MakeNodePath(node, pathutils.JOB_QUEUE_DRAIN_FILE)
1147 def _AssertDrainFile(node, **kwargs):
1148 """Checks for the queue drain file.
1151 AssertCommand(["test", "-f", _NodeQueueDrainFile(node)], node=node, **kwargs)
1154 def TestClusterMasterFailoverWithDrainedQueue():
1155 """gnt-cluster master-failover with drained queue"""
1156 master = qa_config.GetMasterNode()
1157 failovermaster = qa_config.AcquireNode(exclude=master)
1159 # Ensure queue is not drained
1160 for node in [master, failovermaster]:
1161 _AssertDrainFile(node, fail=True)
1163 # Drain queue on failover master
1164 AssertCommand(["touch", _NodeQueueDrainFile(failovermaster)],
1165 node=failovermaster)
1167 cmd = ["gnt-cluster", "master-failover"]
1169 _AssertDrainFile(failovermaster)
1170 AssertCommand(cmd, node=failovermaster)
1171 _AssertDrainFile(master, fail=True)
1172 _AssertDrainFile(failovermaster, fail=True)
1174 # Back to original master node
1175 AssertCommand(cmd, node=master)
1177 failovermaster.Release()
1179 # Ensure queue is not drained
1180 for node in [master, failovermaster]:
1181 _AssertDrainFile(node, fail=True)
1184 def TestClusterCopyfile():
1185 """gnt-cluster copyfile"""
1186 master = qa_config.GetMasterNode()
1188 uniqueid = utils.NewUUID()
1190 # Create temporary file
1191 f = tempfile.NamedTemporaryFile()
1196 # Upload file to master node
1197 testname = qa_utils.UploadFile(master.primary, f.name)
1199 # Copy file to all nodes
1200 AssertCommand(["gnt-cluster", "copyfile", testname])
1201 _CheckFileOnAllNodes(testname, uniqueid)
1203 _RemoveFileFromAllNodes(testname)
1206 def TestClusterCommand():
1207 """gnt-cluster command"""
1208 uniqueid = utils.NewUUID()
1209 rfile = "/tmp/gnt%s" % utils.NewUUID()
1210 rcmd = utils.ShellQuoteArgs(["echo", "-n", uniqueid])
1211 cmd = utils.ShellQuoteArgs(["gnt-cluster", "command",
1212 "%s >%s" % (rcmd, rfile)])
1216 _CheckFileOnAllNodes(rfile, uniqueid)
1218 _RemoveFileFromAllNodes(rfile)
1221 def TestClusterDestroy():
1222 """gnt-cluster destroy"""
1223 AssertCommand(["gnt-cluster", "destroy", "--yes-do-it"])
1226 def TestClusterRepairDiskSizes():
1227 """gnt-cluster repair-disk-sizes"""
1228 AssertCommand(["gnt-cluster", "repair-disk-sizes"])
1231 def TestSetExclStorCluster(newvalue):
1232 """Set the exclusive_storage node parameter at the cluster level.
1234 @type newvalue: bool
1235 @param newvalue: New value of exclusive_storage
1237 @return: The old value of exclusive_storage
1240 es_path = ["Default node parameters", "exclusive_storage"]
1241 oldvalue = _GetClusterField(es_path)
1242 AssertCommand(["gnt-cluster", "modify", "--node-parameters",
1243 "exclusive_storage=%s" % newvalue])
1244 effvalue = _GetClusterField(es_path)
1245 if effvalue != newvalue:
1246 raise qa_error.Error("exclusive_storage has the wrong value: %s instead"
1247 " of %s" % (effvalue, newvalue))
1248 qa_config.SetExclusiveStorage(newvalue)
1252 def TestExclStorSharedPv(node):
1253 """cluster-verify reports LVs that share the same PV with exclusive_storage.
1256 vgname = qa_config.get("vg-name", constants.DEFAULT_VG)
1257 lvname1 = _QA_LV_PREFIX + "vol1"
1258 lvname2 = _QA_LV_PREFIX + "vol2"
1259 node_name = node.primary
1260 AssertCommand(["lvcreate", "-L1G", "-n", lvname1, vgname], node=node_name)
1261 AssertClusterVerify(fail=True, errors=[constants.CV_ENODEORPHANLV])
1262 AssertCommand(["lvcreate", "-L1G", "-n", lvname2, vgname], node=node_name)
1263 AssertClusterVerify(fail=True, errors=[constants.CV_ENODELVM,
1264 constants.CV_ENODEORPHANLV])
1265 AssertCommand(["lvremove", "-f", "/".join([vgname, lvname1])], node=node_name)
1266 AssertCommand(["lvremove", "-f", "/".join([vgname, lvname2])], node=node_name)
1267 AssertClusterVerify()