| # |
| # |
| |
| # Copyright (C) 2010, 2011, 2012 Google Inc. |
| # All rights reserved. |
| # |
| # Redistribution and use in source and binary forms, with or without |
| # modification, are permitted provided that the following conditions are |
| # met: |
| # |
| # 1. Redistributions of source code must retain the above copyright notice, |
| # this list of conditions and the following disclaimer. |
| # |
| # 2. Redistributions in binary form must reproduce the above copyright |
| # notice, this list of conditions and the following disclaimer in the |
| # documentation and/or other materials provided with the distribution. |
| # |
| # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS |
| # IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED |
| # TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
| # PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR |
| # CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, |
| # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
| # PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR |
| # PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF |
| # LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING |
| # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS |
| # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| |
| |
| """QA tests for node groups. |
| |
| """ |
| |
| from ganeti import constants |
| from ganeti import query |
| from ganeti import utils |
| |
| import qa_config |
| import qa_utils |
| |
| from qa_utils import AssertCommand, AssertEqual, GetCommandOutput |
| |
| |
| def GetDefaultGroup(): |
| """Returns the default node group. |
| |
| """ |
| groups = qa_config.get("groups", {}) |
| return groups.get("group-with-nodes", constants.INITIAL_NODE_GROUP_NAME) |
| |
| |
| def TestGroupAddRemoveRename(): |
| """gnt-group add/remove/rename""" |
| existing_group_with_nodes = GetDefaultGroup() |
| |
| (group1, group2, group3) = qa_utils.GetNonexistentGroups(3) |
| |
| AssertCommand(["gnt-group", "add", group1]) |
| AssertCommand(["gnt-group", "add", group2]) |
| AssertCommand(["gnt-group", "add", group2], fail=True) |
| AssertCommand(["gnt-group", "add", existing_group_with_nodes], fail=True) |
| |
| AssertCommand(["gnt-group", "rename", group1, group2], fail=True) |
| AssertCommand(["gnt-group", "rename", group1, group3]) |
| |
| try: |
| AssertCommand(["gnt-group", "rename", existing_group_with_nodes, group1]) |
| |
| AssertCommand(["gnt-group", "remove", group2]) |
| AssertCommand(["gnt-group", "remove", group3]) |
| AssertCommand(["gnt-group", "remove", group1], fail=True) |
| finally: |
| # Try to ensure idempotency re groups that already existed. |
| AssertCommand(["gnt-group", "rename", group1, existing_group_with_nodes]) |
| |
| |
| def TestGroupAddWithOptions(): |
| """gnt-group add with options""" |
| (group1, ) = qa_utils.GetNonexistentGroups(1) |
| |
| AssertCommand(["gnt-group", "add", "--alloc-policy", "notvalid", group1], |
| fail=True) |
| |
| AssertCommand(["gnt-group", "add", "--alloc-policy", "last_resort", |
| "--node-parameters", "oob_program=/bin/true", group1]) |
| |
| AssertCommand(["gnt-group", "remove", group1]) |
| |
| |
| def _GetGroupIPolicy(groupname): |
| """Return the run-time values of the cluster-level instance policy. |
| |
| @type groupname: string |
| @param groupname: node group name |
| @rtype: tuple |
| @return: (policy, specs), where: |
| - policy is a dictionary of the policy values, instance specs excluded |
| - specs is a dictionary containing only the specs, using the internal |
| format (see L{constants.IPOLICY_DEFAULTS} for an example), but without |
| the standard values |
| |
| """ |
| info = qa_utils.GetObjectInfo(["gnt-group", "info", groupname]) |
| assert len(info) == 1 |
| policy = info[0]["Instance policy"] |
| |
| (ret_policy, ret_specs) = qa_utils.ParseIPolicy(policy) |
| |
| # Sanity checks |
| assert "minmax" in ret_specs |
| assert len(ret_specs["minmax"]) > 0 |
| assert len(ret_policy) > 0 |
| return (ret_policy, ret_specs) |
| |
| |
| def _TestGroupSetISpecs(groupname, new_specs=None, diff_specs=None, |
| fail=False, old_values=None): |
| """Change instance specs on a group. |
| |
| At most one of new_specs or diff_specs can be specified. |
| |
| @type groupname: string |
| @param groupname: group name |
| @type new_specs: dict |
| @param new_specs: new complete specs, in the same format returned by |
| L{_GetGroupIPolicy} |
| @type diff_specs: dict |
| @param diff_specs: partial specs, it can be an incomplete specifications, but |
| if min/max specs are specified, their number must match the number of the |
| existing specs |
| @type fail: bool |
| @param fail: if the change is expected to fail |
| @type old_values: tuple |
| @param old_values: (old_policy, old_specs), as returned by |
| L{_GetGroupIPolicy} |
| @return: same as L{_GetGroupIPolicy} |
| |
| """ |
| build_cmd = lambda opts: ["gnt-group", "modify"] + opts + [groupname] |
| get_policy = lambda: _GetGroupIPolicy(groupname) |
| return qa_utils.TestSetISpecs( |
| new_specs=new_specs, diff_specs=diff_specs, |
| get_policy_fn=get_policy, build_cmd_fn=build_cmd, |
| fail=fail, old_values=old_values) |
| |
| |
| def _TestGroupModifyISpecs(groupname): |
| # This test is built on the assumption that the default ipolicy holds for |
| # the node group under test |
| old_values = _GetGroupIPolicy(groupname) |
| samevals = dict((p, 4) for p in constants.ISPECS_PARAMETERS) |
| base_specs = { |
| constants.ISPECS_MINMAX: [{ |
| constants.ISPECS_MIN: samevals, |
| constants.ISPECS_MAX: samevals, |
| }], |
| } |
| mod_values = _TestGroupSetISpecs(groupname, new_specs=base_specs, |
| old_values=old_values) |
| for par in constants.ISPECS_PARAMETERS: |
| # First make sure that the test works with good values |
| good_specs = { |
| constants.ISPECS_MINMAX: [{ |
| constants.ISPECS_MIN: {par: 8}, |
| constants.ISPECS_MAX: {par: 8}, |
| }], |
| } |
| mod_values = _TestGroupSetISpecs(groupname, diff_specs=good_specs, |
| old_values=mod_values) |
| bad_specs = { |
| constants.ISPECS_MINMAX: [{ |
| constants.ISPECS_MIN: {par: 8}, |
| constants.ISPECS_MAX: {par: 4}, |
| }], |
| } |
| _TestGroupSetISpecs(groupname, diff_specs=bad_specs, fail=True, |
| old_values=mod_values) |
| AssertCommand(["gnt-group", "modify", "--ipolicy-bounds-specs", "default", |
| groupname]) |
| AssertEqual(_GetGroupIPolicy(groupname), old_values) |
| |
| # Get the ipolicy command (from the cluster config) |
| mnode = qa_config.GetMasterNode() |
| addcmd = GetCommandOutput(mnode.primary, utils.ShellQuoteArgs([ |
| "gnt-group", "show-ispecs-cmd", "--include-defaults", groupname, |
| ])) |
| modcmd = ["gnt-group", "modify"] |
| opts = addcmd.split() |
| assert opts[0:2] == ["gnt-group", "add"] |
| for k in range(2, len(opts) - 1): |
| if opts[k].startswith("--ipolicy-"): |
| assert k + 2 <= len(opts) |
| modcmd.extend(opts[k:k + 2]) |
| modcmd.append(groupname) |
| # Apply the ipolicy to the group and verify the result |
| AssertCommand(modcmd) |
| new_addcmd = GetCommandOutput(mnode.primary, utils.ShellQuoteArgs([ |
| "gnt-group", "show-ispecs-cmd", groupname, |
| ])) |
| AssertEqual(addcmd, new_addcmd) |
| |
| |
| def _TestGroupModifyIPolicy(groupname): |
| _TestGroupModifyISpecs(groupname) |
| |
| # We assume that the default ipolicy holds |
| (old_policy, old_specs) = _GetGroupIPolicy(groupname) |
| for (par, setval, iname, expval) in [ |
| ("vcpu-ratio", 1.5, None, 1.5), |
| ("spindle-ratio", 1.5, None, 1.5), |
| ("disk-templates", constants.DT_PLAIN, |
| "allowed disk templates", constants.DT_PLAIN) |
| ]: |
| if not iname: |
| iname = par |
| build_cmdline = lambda val: ["gnt-group", "modify", "--ipolicy-" + par, |
| str(val), groupname] |
| |
| AssertCommand(build_cmdline(setval)) |
| (new_policy, new_specs) = _GetGroupIPolicy(groupname) |
| AssertEqual(new_specs, old_specs) |
| for (p, val) in new_policy.items(): |
| if p == iname: |
| AssertEqual(val, expval) |
| else: |
| AssertEqual(val, old_policy[p]) |
| |
| AssertCommand(build_cmdline("default")) |
| (new_policy, new_specs) = _GetGroupIPolicy(groupname) |
| AssertEqual(new_specs, old_specs) |
| AssertEqual(new_policy, old_policy) |
| |
| |
| def TestGroupModify(): |
| """gnt-group modify""" |
| # This tests assumes LVM to be enabled, thus it should skip if |
| # this is not the case |
| if not qa_config.IsStorageTypeSupported(constants.ST_LVM_VG): |
| return |
| (group1, ) = qa_utils.GetNonexistentGroups(1) |
| |
| AssertCommand(["gnt-group", "add", group1]) |
| |
| try: |
| _TestGroupModifyIPolicy(group1) |
| AssertCommand(["gnt-group", "modify", "--alloc-policy", "unallocable", |
| "--node-parameters", "oob_program=/bin/false", group1]) |
| AssertCommand(["gnt-group", "modify", |
| "--alloc-policy", "notvalid", group1], fail=True) |
| AssertCommand(["gnt-group", "modify", |
| "--node-parameters", "spindle_count=10", group1]) |
| if qa_config.TestEnabled("htools"): |
| AssertCommand(["hbal", "-L", "-G", group1]) |
| AssertCommand(["gnt-group", "modify", |
| "--node-parameters", "spindle_count=default", group1]) |
| finally: |
| AssertCommand(["gnt-group", "remove", group1]) |
| |
| |
| def TestGroupList(): |
| """gnt-group list""" |
| qa_utils.GenericQueryTest("gnt-group", query.GROUP_FIELDS.keys()) |
| |
| |
| def TestGroupListFields(): |
| """gnt-group list-fields""" |
| qa_utils.GenericQueryFieldsTest("gnt-group", query.GROUP_FIELDS.keys()) |
| |
| |
| def TestAssignNodesIncludingSplit(orig_group, node1, node2): |
| """gnt-group assign-nodes --force |
| |
| Expects node1 and node2 to be primary and secondary for a common instance. |
| |
| """ |
| assert node1 != node2 |
| |
| (other_group, ) = qa_utils.GetNonexistentGroups(1) |
| |
| master_node = qa_config.GetMasterNode().primary |
| |
| def AssertInGroup(group, nodes): |
| real_output = GetCommandOutput(master_node, |
| "gnt-node list --no-headers -o group " + |
| utils.ShellQuoteArgs(nodes)) |
| AssertEqual(real_output.splitlines(), [group] * len(nodes)) |
| |
| AssertInGroup(orig_group, [node1, node2]) |
| AssertCommand(["gnt-group", "add", other_group]) |
| |
| try: |
| AssertCommand(["gnt-group", "assign-nodes", other_group, node1, node2]) |
| AssertInGroup(other_group, [node1, node2]) |
| |
| # This should fail because moving node1 to orig_group would leave their |
| # common instance split between orig_group and other_group. |
| AssertCommand(["gnt-group", "assign-nodes", orig_group, node1], fail=True) |
| AssertInGroup(other_group, [node1, node2]) |
| |
| AssertCommand(["gnt-group", "assign-nodes", "--force", orig_group, node1]) |
| AssertInGroup(orig_group, [node1]) |
| AssertInGroup(other_group, [node2]) |
| |
| AssertCommand(["gnt-group", "assign-nodes", orig_group, node2]) |
| AssertInGroup(orig_group, [node1, node2]) |
| finally: |
| AssertCommand(["gnt-group", "remove", other_group]) |