Revision ec996117
b/qa/ganeti-qa.py | ||
---|---|---|
523 | 523 |
|
524 | 524 |
|
525 | 525 |
def _BuildSpecDict(par, mn, st, mx): |
526 |
return {par: {"min": mn, "std": st, "max": mx}} |
|
526 |
return { |
|
527 |
"min": {par: mn}, |
|
528 |
"max": {par: mx}, |
|
529 |
"std": {par: st}, |
|
530 |
} |
|
527 | 531 |
|
528 | 532 |
|
529 | 533 |
def TestIPolicyPlainInstance(): |
... | ... | |
534 | 538 |
return |
535 | 539 |
|
536 | 540 |
# This test assumes that the group policy is empty |
537 |
(_, old_specs) = qa_cluster.TestClusterSetISpecs({})
|
|
541 |
(_, old_specs) = qa_cluster.TestClusterSetISpecs() |
|
538 | 542 |
node = qa_config.AcquireNode() |
539 | 543 |
try: |
540 |
# Log of policy changes, list of tuples: (change, policy_violated) |
|
544 |
# Log of policy changes, list of tuples: |
|
545 |
# (full_change, incremental_change, policy_violated) |
|
541 | 546 |
history = [] |
542 | 547 |
instance = qa_instance.TestInstanceAddWithPlainDisk([node]) |
543 | 548 |
try: |
... | ... | |
547 | 552 |
(iminval, imaxval) = qa_instance.GetInstanceSpec(instance.name, par) |
548 | 553 |
# Some specs must be multiple of 4 |
549 | 554 |
new_spec = _BuildSpecDict(par, imaxval + 4, imaxval + 4, imaxval + 4) |
550 |
history.append((new_spec, True)) |
|
551 |
qa_cluster.TestClusterSetISpecs(new_spec) |
|
555 |
history.append((None, new_spec, True))
|
|
556 |
qa_cluster.TestClusterSetISpecs(diff_specs=new_spec)
|
|
552 | 557 |
qa_cluster.AssertClusterVerify(warnings=policyerror) |
553 | 558 |
if iminval > 0: |
554 | 559 |
# Some specs must be multiple of 4 |
... | ... | |
557 | 562 |
else: |
558 | 563 |
upper = iminval - 1 |
559 | 564 |
new_spec = _BuildSpecDict(par, 0, upper, upper) |
560 |
history.append((new_spec, True)) |
|
561 |
qa_cluster.TestClusterSetISpecs(new_spec) |
|
565 |
history.append((None, new_spec, True))
|
|
566 |
qa_cluster.TestClusterSetISpecs(diff_specs=new_spec)
|
|
562 | 567 |
qa_cluster.AssertClusterVerify(warnings=policyerror) |
563 |
qa_cluster.TestClusterSetISpecs(old_specs) |
|
564 |
history.append((old_specs, False)) |
|
568 |
qa_cluster.TestClusterSetISpecs(new_specs=old_specs)
|
|
569 |
history.append((old_specs, None, False))
|
|
565 | 570 |
qa_instance.TestInstanceRemove(instance) |
566 | 571 |
finally: |
567 | 572 |
instance.Release() |
568 | 573 |
|
569 | 574 |
# Now we replay the same policy changes, and we expect that the instance |
570 | 575 |
# cannot be created for the cases where we had a policy violation above |
571 |
for (change, failed) in history: |
|
572 |
qa_cluster.TestClusterSetISpecs(change) |
|
576 |
for (new_specs, diff_specs, failed) in history: |
|
577 |
qa_cluster.TestClusterSetISpecs(new_specs=new_specs, |
|
578 |
diff_specs=diff_specs) |
|
573 | 579 |
if failed: |
574 | 580 |
qa_instance.TestInstanceAddWithPlainDisk([node], fail=True) |
575 | 581 |
# Instance creation with no policy violation has been tested already |
b/qa/qa_cluster.py | ||
---|---|---|
532 | 532 |
@rtype: tuple |
533 | 533 |
@return: (policy, specs), where: |
534 | 534 |
- policy is a dictionary of the policy values, instance specs excluded |
535 |
- specs is dict of dict, specs[par][key] is a spec value, where key is
|
|
535 |
- specs is dict of dict, specs[key][par] is a spec value, where key is
|
|
536 | 536 |
"min", "max", or "std" |
537 | 537 |
|
538 | 538 |
""" |
... | ... | |
541 | 541 |
(ret_policy, ret_specs) = qa_utils.ParseIPolicy(policy) |
542 | 542 |
|
543 | 543 |
# Sanity checks |
544 |
assert len(ret_specs) > 0 |
|
545 |
good = all("min" in d and "std" in d and "max" in d |
|
546 |
for d in ret_specs.values()) |
|
547 |
assert good, "Missing item in specs: %s" % ret_specs |
|
544 |
assert "min" in ret_specs and "std" in ret_specs and "max" in ret_specs |
|
548 | 545 |
assert len(ret_policy) > 0 |
549 | 546 |
return (ret_policy, ret_specs) |
550 | 547 |
|
... | ... | |
606 | 603 |
AssertEqual(eff_policy[p], old_policy[p]) |
607 | 604 |
|
608 | 605 |
|
609 |
def TestClusterSetISpecs(new_specs, fail=False, old_values=None): |
|
606 |
def TestClusterSetISpecs(new_specs=None, diff_specs=None, fail=False, |
|
607 |
old_values=None): |
|
610 | 608 |
"""Change instance specs. |
611 | 609 |
|
612 |
@type new_specs: dict of dict |
|
613 |
@param new_specs: new_specs[par][key], where key is "min", "max", "std". It |
|
614 |
can be an empty dictionary. |
|
610 |
At most one of new_specs or diff_specs can be specified. |
|
611 |
|
|
612 |
@type new_specs: dict |
|
613 |
@param new_specs: new complete specs, in the same format returned by |
|
614 |
L{_GetClusterIPolicy} |
|
615 |
@type diff_specs: dict |
|
616 |
@param diff_specs: diff_specs[key][par], where key is "min", "max", "std". It |
|
617 |
can be an incomplete specifications or an empty dictionary. |
|
615 | 618 |
@type fail: bool |
616 | 619 |
@param fail: if the change is expected to fail |
617 | 620 |
@type old_values: tuple |
618 | 621 |
@param old_values: (old_policy, old_specs), as returned by |
619 |
L{_GetClusterIPolicy} |
|
622 |
L{_GetClusterIPolicy}
|
|
620 | 623 |
@return: same as L{_GetClusterIPolicy} |
621 | 624 |
|
622 | 625 |
""" |
623 | 626 |
build_cmd = lambda opts: ["gnt-cluster", "modify"] + opts |
624 |
return qa_utils.TestSetISpecs(new_specs, get_policy_fn=_GetClusterIPolicy, |
|
625 |
build_cmd_fn=build_cmd, fail=fail, |
|
626 |
old_values=old_values) |
|
627 |
return qa_utils.TestSetISpecs( |
|
628 |
new_specs=new_specs, diff_specs=diff_specs, |
|
629 |
get_policy_fn=_GetClusterIPolicy, build_cmd_fn=build_cmd, |
|
630 |
fail=fail, old_values=old_values) |
|
627 | 631 |
|
628 | 632 |
|
629 | 633 |
def TestClusterModifyISpecs(): |
... | ... | |
646 | 650 |
(False, 0, 4, "a"), |
647 | 651 |
# This is to restore the old values |
648 | 652 |
(True, |
649 |
cur_specs[par]["min"], cur_specs[par]["std"], cur_specs[par]["max"])
|
|
653 |
cur_specs["min"][par], cur_specs["std"][par], cur_specs["max"][par])
|
|
650 | 654 |
] |
651 | 655 |
for (good, mn, st, mx) in test_values: |
652 |
new_vals = {par: {"min": str(mn), "std": str(st), "max": str(mx)}} |
|
656 |
new_vals = { |
|
657 |
"min": {par: mn}, |
|
658 |
"std": {par: st}, |
|
659 |
"max": {par: mx} |
|
660 |
} |
|
653 | 661 |
cur_state = (cur_policy, cur_specs) |
654 | 662 |
# We update cur_specs, as we've copied the values to restore already |
655 |
(cur_policy, cur_specs) = TestClusterSetISpecs(new_vals, fail=not good,
|
|
656 |
old_values=cur_state)
|
|
663 |
(cur_policy, cur_specs) = TestClusterSetISpecs( |
|
664 |
diff_specs=new_vals, fail=not good, old_values=cur_state)
|
|
657 | 665 |
|
658 | 666 |
# Get the ipolicy command |
659 | 667 |
mnode = qa_config.GetMasterNode() |
b/qa/qa_group.py | ||
---|---|---|
87 | 87 |
@rtype: tuple |
88 | 88 |
@return: (policy, specs), where: |
89 | 89 |
- policy is a dictionary of the policy values, instance specs excluded |
90 |
- specs is dict of dict, specs[par][key] is a spec value, where key is
|
|
90 |
- specs is dict of dict, specs[key][par] is a spec value, where key is
|
|
91 | 91 |
"min" or "max" |
92 | 92 |
|
93 | 93 |
""" |
... | ... | |
98 | 98 |
(ret_policy, ret_specs) = qa_utils.ParseIPolicy(policy) |
99 | 99 |
|
100 | 100 |
# Sanity checks |
101 |
assert len(ret_specs) > 0 |
|
102 |
good = all("min" in d and "max" in d |
|
103 |
for d in ret_specs.values()) |
|
104 |
assert good, "Missing item in specs: %s" % ret_specs |
|
101 |
assert "min" in ret_specs and "max" in ret_specs |
|
105 | 102 |
assert len(ret_policy) > 0 |
106 | 103 |
return (ret_policy, ret_specs) |
107 | 104 |
|
108 | 105 |
|
109 |
def _TestGroupSetISpecs(groupname, new_specs, fail=False, old_values=None): |
|
106 |
def _TestGroupSetISpecs(groupname, new_specs=None, diff_specs=None, |
|
107 |
fail=False, old_values=None): |
|
110 | 108 |
"""Change instance specs on a group. |
111 | 109 |
|
110 |
At most one of new_specs or diff_specs can be specified. |
|
111 |
|
|
112 | 112 |
@type groupname: string |
113 | 113 |
@param groupname: group name |
114 |
@type new_specs: dict of dict |
|
115 |
@param new_specs: new_specs[par][key], where key is "min", "max", "std". It |
|
116 |
can be an empty dictionary. |
|
114 |
@type new_specs: dict |
|
115 |
@param new_specs: new complete specs, in the same format returned by |
|
116 |
L{_GetGroupIPolicy} |
|
117 |
@type diff_specs: dict |
|
118 |
@param diff_specs: diff_specs[key][par], where key is "min", "max". It |
|
119 |
can be an incomplete specifications or an empty dictionary. |
|
117 | 120 |
@type fail: bool |
118 | 121 |
@param fail: if the change is expected to fail |
119 | 122 |
@type old_values: tuple |
120 | 123 |
@param old_values: (old_policy, old_specs), as returned by |
121 |
L{_GetGroupIPolicy} |
|
124 |
L{_GetGroupIPolicy}
|
|
122 | 125 |
@return: same as L{_GetGroupIPolicy} |
123 | 126 |
|
124 | 127 |
""" |
125 | 128 |
build_cmd = lambda opts: ["gnt-group", "modify"] + opts + [groupname] |
126 | 129 |
get_policy = lambda: _GetGroupIPolicy(groupname) |
127 |
return qa_utils.TestSetISpecs(new_specs, get_policy_fn=get_policy, |
|
128 |
build_cmd_fn=build_cmd, fail=fail, |
|
129 |
old_values=old_values) |
|
130 |
return qa_utils.TestSetISpecs( |
|
131 |
new_specs=new_specs, diff_specs=diff_specs, |
|
132 |
get_policy_fn=get_policy, build_cmd_fn=build_cmd, |
|
133 |
fail=fail, old_values=old_values) |
|
130 | 134 |
|
131 | 135 |
|
132 | 136 |
def _TestGroupModifyISpecs(groupname): |
133 | 137 |
# This test is built on the assumption that the default ipolicy holds for |
134 | 138 |
# the node group under test |
135 | 139 |
old_values = _GetGroupIPolicy(groupname) |
136 |
mod_values = _TestGroupSetISpecs(groupname,
|
|
137 |
dict((p, {"min": 4, "max": 4})
|
|
138 |
for p in constants.ISPECS_PARAMETERS),
|
|
140 |
samevals = dict((p, 4) for p in constants.ISPECS_PARAMETERS)
|
|
141 |
base_specs = {"min": samevals, "max": samevals}
|
|
142 |
mod_values = _TestGroupSetISpecs(groupname, new_specs=base_specs,
|
|
139 | 143 |
old_values=old_values) |
140 | 144 |
for par in constants.ISPECS_PARAMETERS: |
141 | 145 |
# First make sure that the test works with good values |
142 |
mod_values = _TestGroupSetISpecs(groupname, {par: {"min": 8, "max": 8}}, |
|
146 |
good_specs = {"min": {par: 8}, "max": {par: 8}} |
|
147 |
mod_values = _TestGroupSetISpecs(groupname, diff_specs=good_specs, |
|
143 | 148 |
old_values=mod_values) |
144 |
_TestGroupSetISpecs(groupname, {par: {"min": 8, "max": 4}}, |
|
145 |
fail=True, old_values=mod_values) |
|
149 |
bad_specs = {"min": {par: 8}, "max": {par: 4}} |
|
150 |
_TestGroupSetISpecs(groupname, diff_specs=bad_specs, fail=True, |
|
151 |
old_values=mod_values) |
|
146 | 152 |
AssertCommand(["gnt-group", "modify", "--ipolicy-bounds-specs", "default", |
147 | 153 |
groupname]) |
148 | 154 |
AssertEqual(_GetGroupIPolicy(groupname), old_values) |
b/qa/qa_utils.py | ||
---|---|---|
23 | 23 |
|
24 | 24 |
""" |
25 | 25 |
|
26 |
import copy |
|
26 | 27 |
import operator |
27 | 28 |
import os |
28 | 29 |
import random |
... | ... | |
779 | 780 |
return path |
780 | 781 |
|
781 | 782 |
|
782 |
def _GetParameterOptions(key, specs, old_specs):
|
|
783 |
def _GetParameterOptions(specs): |
|
783 | 784 |
"""Helper to build policy options.""" |
784 |
values = ["%s=%s" % (par, keyvals[key]) |
|
785 |
for (par, keyvals) in specs.items() |
|
786 |
if key in keyvals] |
|
787 |
if old_specs: |
|
788 |
present_pars = frozenset(par |
|
789 |
for (par, keyvals) in specs.items() |
|
790 |
if key in keyvals) |
|
791 |
values.extend("%s=%s" % (par, keyvals[key]) |
|
792 |
for (par, keyvals) in old_specs.items() |
|
793 |
if key in keyvals and par not in present_pars) |
|
785 |
values = ["%s=%s" % (par, val) |
|
786 |
for (par, val) in specs.items()] |
|
794 | 787 |
return ",".join(values) |
795 | 788 |
|
796 | 789 |
|
797 |
def TestSetISpecs(new_specs, get_policy_fn=None, build_cmd_fn=None,
|
|
798 |
fail=False, old_values=None): |
|
790 |
def TestSetISpecs(new_specs=None, diff_specs=None, get_policy_fn=None,
|
|
791 |
build_cmd_fn=None, fail=False, old_values=None):
|
|
799 | 792 |
"""Change instance specs for an object. |
800 | 793 |
|
801 |
@type new_specs: dict of dict |
|
802 |
@param new_specs: new_specs[par][key], where key is "min", "max", "std". It |
|
803 |
can be an empty dictionary. |
|
794 |
At most one of new_specs or diff_specs can be specified. |
|
795 |
|
|
796 |
@type new_specs: dict |
|
797 |
@param new_specs: new complete specs, in the same format returned by |
|
798 |
L{ParseIPolicy}. |
|
799 |
@type diff_specs: dict |
|
800 |
@param diff_specs: diff_specs[key][par], where key is "min", "max", "std". It |
|
801 |
can be an incomplete specifications or an empty dictionary. |
|
804 | 802 |
@type get_policy_fn: function |
805 | 803 |
@param get_policy_fn: function that returns the current policy as in |
806 |
L{qa_cluster._GetClusterIPolicy}
|
|
804 |
L{ParseIPolicy}
|
|
807 | 805 |
@type build_cmd_fn: function |
808 | 806 |
@param build_cmd_fn: function that return the full command line from the |
809 | 807 |
options alone |
... | ... | |
811 | 809 |
@param fail: if the change is expected to fail |
812 | 810 |
@type old_values: tuple |
813 | 811 |
@param old_values: (old_policy, old_specs), as returned by |
814 |
L{qa_cluster._GetClusterIPolicy}
|
|
815 |
@return: same as L{qa_cluster._GetClusterIPolicy}
|
|
812 |
L{ParseIPolicy}
|
|
813 |
@return: same as L{ParseIPolicy}
|
|
816 | 814 |
|
817 | 815 |
""" |
818 | 816 |
assert get_policy_fn is not None |
819 | 817 |
assert build_cmd_fn is not None |
818 |
assert new_specs is None or diff_specs is None |
|
820 | 819 |
|
821 | 820 |
if old_values: |
822 | 821 |
(old_policy, old_specs) = old_values |
823 | 822 |
else: |
824 | 823 |
(old_policy, old_specs) = get_policy_fn() |
824 |
|
|
825 |
if diff_specs: |
|
826 |
new_specs = copy.deepcopy(old_specs) |
|
827 |
for (key, parvals) in diff_specs.items(): |
|
828 |
for (par, val) in parvals.items(): |
|
829 |
new_specs[key][par] = val |
|
830 |
|
|
825 | 831 |
if new_specs: |
826 | 832 |
cmd = [] |
827 |
if any(("min" in val or "max" in val) for val in new_specs.values()): |
|
833 |
if (diff_specs is None or |
|
834 |
("min" in diff_specs or "max" in diff_specs)): |
|
828 | 835 |
minmax_opt_items = [] |
829 | 836 |
for key in ["min", "max"]: |
830 |
keyopt = _GetParameterOptions(key, new_specs, old_specs)
|
|
837 |
keyopt = _GetParameterOptions(new_specs[key])
|
|
831 | 838 |
minmax_opt_items.append("%s:%s" % (key, keyopt)) |
832 | 839 |
cmd.extend([ |
833 | 840 |
"--ipolicy-bounds-specs", |
834 | 841 |
"/".join(minmax_opt_items) |
835 | 842 |
]) |
836 |
std_opt = _GetParameterOptions("std", new_specs, {}) |
|
843 |
if diff_specs: |
|
844 |
std_source = diff_specs |
|
845 |
else: |
|
846 |
std_source = new_specs |
|
847 |
std_opt = _GetParameterOptions(std_source.get("std", {})) |
|
837 | 848 |
if std_opt: |
838 | 849 |
cmd.extend(["--ipolicy-std-specs", std_opt]) |
839 | 850 |
AssertCommand(build_cmd_fn(cmd), fail=fail) |
840 | 851 |
|
841 |
# Check the new state |
|
842 |
(eff_policy, eff_specs) = get_policy_fn() |
|
843 |
AssertEqual(eff_policy, old_policy) |
|
844 |
if fail: |
|
845 |
AssertEqual(eff_specs, old_specs) |
|
852 |
# Check the new state |
|
853 |
(eff_policy, eff_specs) = get_policy_fn() |
|
854 |
AssertEqual(eff_policy, old_policy) |
|
855 |
if fail: |
|
856 |
AssertEqual(eff_specs, old_specs) |
|
857 |
else: |
|
858 |
AssertEqual(eff_specs, new_specs) |
|
859 |
|
|
846 | 860 |
else: |
847 |
for par in eff_specs: |
|
848 |
for key in eff_specs[par]: |
|
849 |
if par in new_specs and key in new_specs[par]: |
|
850 |
AssertEqual(int(eff_specs[par][key]), int(new_specs[par][key])) |
|
851 |
else: |
|
852 |
AssertEqual(int(eff_specs[par][key]), int(old_specs[par][key])) |
|
861 |
(eff_policy, eff_specs) = (old_policy, old_specs) |
|
862 |
|
|
853 | 863 |
return (eff_policy, eff_specs) |
854 | 864 |
|
855 | 865 |
|
... | ... | |
861 | 871 |
@rtype: tuple |
862 | 872 |
@return: (policy, specs), where: |
863 | 873 |
- policy is a dictionary of the policy values, instance specs excluded |
864 |
- specs is dict of dict, specs[par][key] is a spec value, where key is
|
|
874 |
- specs is dict of dict, specs[key][par] is a spec value, where key is
|
|
865 | 875 |
"min", "max", or "std" |
866 | 876 |
|
867 | 877 |
""" |
... | ... | |
870 | 880 |
ispec_keys = constants.ISPECS_MINMAX_KEYS | frozenset([constants.ISPECS_STD]) |
871 | 881 |
for (key, val) in policy.items(): |
872 | 882 |
if key in ispec_keys: |
873 |
for (par, pval) in val.items(): |
|
874 |
d = ret_specs.setdefault(par, {}) |
|
875 |
d[key] = pval |
|
883 |
ret_specs[key] = val |
|
876 | 884 |
else: |
877 | 885 |
ret_policy[key] = val |
878 | 886 |
return (ret_policy, ret_specs) |
Also available in: Unified diff