Skip to content

Commit 11849aa

Browse files
authored
Merge pull request #638 from averevki/defaults_overrides_merge_strategy
Add tests for D&O merge strategy
2 parents 5f9d384 + 5804d44 commit 11849aa

17 files changed

+364
-2
lines changed

testsuite/kuadrant/policy/__init__.py

+8
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,19 @@
11
"""Contains Base class for policies"""
22

33
from dataclasses import dataclass
4+
from enum import Enum
45

56
from testsuite.kubernetes import KubernetesObject
67
from testsuite.utils import check_condition
78

89

10+
class Strategy(Enum):
11+
"""Class for merge strategies of defaults and overrides."""
12+
13+
ATOMIC = "atomic"
14+
MERGE = "merge"
15+
16+
917
@dataclass
1018
class CelPredicate:
1119
"""Dataclass that references CEL predicate e.g. auth.identity.anonymous == 'true'"""

testsuite/kuadrant/policy/authorization/auth_policy.py

+10-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
from testsuite.utils import asdict
1010
from .auth_config import AuthConfig
1111
from .sections import ResponseSection
12-
from .. import Policy, CelPredicate
12+
from .. import Policy, CelPredicate, Strategy
1313
from . import Pattern
1414

1515

@@ -49,6 +49,15 @@ def add_rule(self, when: list[CelPredicate]):
4949
self.model.spec.setdefault("when", [])
5050
self.model.spec["when"].extend([asdict(x) for x in when])
5151

52+
@modify
53+
def strategy(self, strategy: Strategy) -> None:
54+
"""Add strategy type to default or overrides spec"""
55+
if self.spec_section is None:
56+
raise TypeError("Strategy can only be set on defaults or overrides")
57+
58+
self.spec_section["strategy"] = strategy.value
59+
self.spec_section = None
60+
5261
@property
5362
def auth_section(self):
5463
if self.spec_section is None:

testsuite/kuadrant/policy/rate_limit.py

+10-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
from testsuite.gateway import Referencable
88
from testsuite.kubernetes import modify
99
from testsuite.kubernetes.client import KubernetesClient
10-
from testsuite.kuadrant.policy import Policy, CelPredicate, CelExpression
10+
from testsuite.kuadrant.policy import Policy, CelPredicate, CelExpression, Strategy
1111
from testsuite.utils import asdict
1212

1313

@@ -72,6 +72,15 @@ def add_limit(
7272
self.spec_section.setdefault("limits", {})[name] = limit
7373
self.spec_section = None
7474

75+
@modify
76+
def strategy(self, strategy: Strategy) -> None:
77+
"""Add strategy type to default or overrides spec"""
78+
if self.spec_section is None:
79+
raise TypeError("Strategy can only be set on defaults or overrides")
80+
81+
self.spec_section["strategy"] = strategy.value
82+
self.spec_section = None
83+
7584
@property
7685
def defaults(self):
7786
"""Add new rule into the `defaults` RateLimitPolicy section"""

testsuite/tests/singlecluster/defaults/merge/__init__.py

Whitespace-only changes.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
"""Conftest for defaults merge strategy tests"""
2+
3+
import pytest
4+
5+
6+
@pytest.fixture(scope="module")
7+
def route(route, backend):
8+
"""Add 2 backend rules for specific backend paths"""
9+
route.remove_all_rules()
10+
route.add_backend(backend, "/get")
11+
route.add_backend(backend, "/anything")
12+
return route

testsuite/tests/singlecluster/defaults/merge/same_target/__init__.py

Whitespace-only changes.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
"""Setup conftest for policy merge on the same targets"""
2+
3+
import pytest
4+
5+
from testsuite.kuadrant.policy import CelPredicate, Strategy
6+
from testsuite.kuadrant.policy.rate_limit import Limit, RateLimitPolicy
7+
8+
LIMIT = Limit(4, "10s")
9+
MERGE_LIMIT = Limit(2, "10s")
10+
MERGE_LIMIT2 = Limit(6, "10s")
11+
12+
13+
@pytest.fixture(scope="module")
14+
def rate_limit(cluster, blame, module_label, route):
15+
"""Add a RateLimitPolicy targeting the first HTTPRouteRule."""
16+
rate_limit = RateLimitPolicy.create_instance(cluster, blame("sp"), route, labels={"testRun": module_label})
17+
rate_limit.add_limit("basic", [LIMIT], when=[CelPredicate("request.path == '/get'")])
18+
return rate_limit
19+
20+
21+
@pytest.fixture(scope="module")
22+
def default_merge_rate_limit(cluster, blame, module_label, route):
23+
"""Add a RateLimitPolicy targeting the first HTTPRouteRule."""
24+
policy = RateLimitPolicy.create_instance(cluster, blame("dmp"), route, labels={"testRun": module_label})
25+
policy.defaults.add_limit("basic", [MERGE_LIMIT], when=[CelPredicate("request.path == '/get'")])
26+
policy.defaults.add_limit("merge", [MERGE_LIMIT2], when=[CelPredicate("request.path == '/anything'")])
27+
policy.defaults.strategy(Strategy.MERGE)
28+
return policy
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
"""Test defaults policy aimed at the same resource uses the oldest policy."""
2+
3+
import pytest
4+
5+
from testsuite.kuadrant.policy import has_condition
6+
from .conftest import MERGE_LIMIT, MERGE_LIMIT2
7+
8+
pytestmark = [pytest.mark.kuadrant_only, pytest.mark.limitador]
9+
10+
11+
@pytest.fixture(scope="module", autouse=True)
12+
def commit(request, route, rate_limit, default_merge_rate_limit): # pylint: disable=unused-argument
13+
"""Commits RateLimitPolicy after the HTTPRoute is created"""
14+
for policy in [rate_limit, default_merge_rate_limit]: # Forcing order of creation.
15+
request.addfinalizer(policy.delete)
16+
policy.commit()
17+
policy.wait_for_accepted()
18+
19+
20+
def test_multiple_policies_merge_default_ab(client, rate_limit, default_merge_rate_limit):
21+
"""Test RateLimitPolicy with merge defaults being enforced due to age"""
22+
assert rate_limit.wait_until(
23+
has_condition(
24+
"Enforced",
25+
"False",
26+
"Overridden",
27+
"RateLimitPolicy is overridden by "
28+
f"[{default_merge_rate_limit.namespace()}/{default_merge_rate_limit.name()}]",
29+
)
30+
)
31+
32+
responses = client.get_many("/get", MERGE_LIMIT.limit)
33+
responses.assert_all(status_code=200)
34+
assert client.get("/get").status_code == 429
35+
36+
responses = client.get_many("/anything", MERGE_LIMIT2.limit)
37+
responses.assert_all(status_code=200)
38+
assert client.get("/anything").status_code == 429
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
"""Test defaults policy aimed at the same resource uses the oldest policy."""
2+
3+
import pytest
4+
5+
from testsuite.kuadrant.policy import has_condition
6+
from .conftest import LIMIT, MERGE_LIMIT2
7+
8+
pytestmark = [pytest.mark.kuadrant_only, pytest.mark.limitador]
9+
10+
11+
@pytest.fixture(scope="module", autouse=True)
12+
def commit(request, route, rate_limit, default_merge_rate_limit): # pylint: disable=unused-argument
13+
"""Commits RateLimitPolicy after the HTTPRoute is created"""
14+
for policy in [default_merge_rate_limit, rate_limit]: # Forcing order of creation.
15+
request.addfinalizer(policy.delete)
16+
policy.commit()
17+
policy.wait_for_accepted()
18+
19+
20+
def test_multiple_policies_merge_default_ba(client, default_merge_rate_limit):
21+
"""Test RateLimitPolicy with merge defaults being ignored due to age"""
22+
assert default_merge_rate_limit.wait_until(
23+
has_condition("Enforced", "True", "Enforced", "RateLimitPolicy has been partially enforced")
24+
)
25+
26+
responses = client.get_many("/get", LIMIT.limit)
27+
responses.assert_all(status_code=200)
28+
assert client.get("/get").status_code == 429
29+
30+
responses = client.get_many("/anything", MERGE_LIMIT2.limit)
31+
responses.assert_all(status_code=200)
32+
assert client.get("/anything").status_code == 429
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
"""Test gateway level default merging with and being partially overriden by another policy."""
2+
3+
import pytest
4+
5+
from testsuite.kuadrant.policy import CelPredicate, has_condition
6+
from testsuite.kuadrant.policy.rate_limit import RateLimitPolicy, Limit, Strategy
7+
8+
pytestmark = [pytest.mark.kuadrant_only, pytest.mark.limitador]
9+
10+
11+
@pytest.fixture(scope="module")
12+
def rate_limit(rate_limit):
13+
"""Create a RateLimitPolicy with a basic limit with same target as one default."""
14+
when = CelPredicate("request.path == '/get'")
15+
rate_limit.add_limit("route_limit", [Limit(5, "5s")], when=[when])
16+
return rate_limit
17+
18+
19+
@pytest.fixture(scope="module")
20+
def global_rate_limit(cluster, blame, module_label, gateway):
21+
"""Create a RateLimitPolicy with default policies and a merge strategy."""
22+
global_rate_limit = RateLimitPolicy.create_instance(
23+
cluster, blame("limit"), gateway, labels={"testRun": module_label}
24+
)
25+
gateway_when = CelPredicate("request.path == '/anything'")
26+
global_rate_limit.defaults.add_limit("gateway_limit", [Limit(3, "5s")], when=[gateway_when])
27+
route_when = CelPredicate("request.path == '/get'")
28+
global_rate_limit.defaults.add_limit("route_limit", [Limit(10, "5s")], when=[route_when])
29+
global_rate_limit.defaults.strategy(Strategy.MERGE)
30+
return global_rate_limit
31+
32+
33+
@pytest.fixture(scope="module", autouse=True)
34+
def commit(request, route, rate_limit, global_rate_limit): # pylint: disable=unused-argument
35+
"""Commits RateLimitPolicy after the HTTPRoute is created"""
36+
for policy in [global_rate_limit, rate_limit]: # Forcing order of creation.
37+
request.addfinalizer(policy.delete)
38+
policy.commit()
39+
policy.wait_for_ready()
40+
41+
42+
@pytest.mark.parametrize("rate_limit", ["gateway", "route"], indirect=True)
43+
def test_gateway_default_merge(client, global_rate_limit):
44+
"""Test Gateway default policy being partially overriden when another policy with the same target is created."""
45+
assert global_rate_limit.wait_until(
46+
has_condition("Enforced", "True", "Enforced", "RateLimitPolicy has been partially enforced")
47+
)
48+
49+
get = client.get_many("/get", 5)
50+
get.assert_all(status_code=200)
51+
assert client.get("/get").status_code == 429
52+
53+
anything = client.get_many("/anything", 3)
54+
anything.assert_all(status_code=200)
55+
assert client.get("/anything").status_code == 429

testsuite/tests/singlecluster/overrides/merge/__init__.py

Whitespace-only changes.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
"""Conftest for overrides merge strategy tests"""
2+
3+
import pytest
4+
5+
6+
@pytest.fixture(scope="module")
7+
def route(route, backend):
8+
"""Add 2 backend rules for specific backend paths"""
9+
route.remove_all_rules()
10+
route.add_backend(backend, "/get")
11+
route.add_backend(backend, "/anything")
12+
return route

testsuite/tests/singlecluster/overrides/merge/same_target/__init__.py

Whitespace-only changes.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
"""Setup conftest for policy override on the same targets"""
2+
3+
import pytest
4+
5+
from testsuite.kuadrant.policy import CelPredicate, Strategy
6+
from testsuite.kuadrant.policy.rate_limit import Limit, RateLimitPolicy
7+
8+
LIMIT = Limit(4, "5s")
9+
OVERRIDE_LIMIT = Limit(6, "5s")
10+
OVERRIDE_LIMIT2 = Limit(2, "5s")
11+
12+
13+
@pytest.fixture(scope="module")
14+
def rate_limit(cluster, blame, module_label, route):
15+
"""Add a RateLimitPolicy targeting the first HTTPRouteRule."""
16+
rate_limit = RateLimitPolicy.create_instance(cluster, blame("sp"), route, labels={"testRun": module_label})
17+
rate_limit.add_limit("basic", [LIMIT], when=[CelPredicate("request.path == '/get'")])
18+
return rate_limit
19+
20+
21+
@pytest.fixture(scope="module")
22+
def override_merge_rate_limit(cluster, blame, module_label, route):
23+
"""Add a RateLimitPolicy targeting the first HTTPRouteRule."""
24+
policy = RateLimitPolicy.create_instance(cluster, blame("omp"), route, labels={"testRun": module_label})
25+
policy.overrides.add_limit("basic", [OVERRIDE_LIMIT], when=[CelPredicate("request.path == '/get'")])
26+
policy.overrides.add_limit("override", [OVERRIDE_LIMIT2], when=[CelPredicate("request.path == '/anything'")])
27+
policy.overrides.strategy(Strategy.MERGE)
28+
return policy
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
"""Test override policy aimed at the same resource always takes precedence."""
2+
3+
import pytest
4+
5+
from testsuite.kuadrant.policy import has_condition
6+
from .conftest import OVERRIDE_LIMIT, OVERRIDE_LIMIT2
7+
8+
pytestmark = [pytest.mark.kuadrant_only, pytest.mark.limitador]
9+
10+
11+
@pytest.fixture(scope="module", autouse=True)
12+
def commit(request, route, rate_limit, override_merge_rate_limit): # pylint: disable=unused-argument
13+
"""Commits RateLimitPolicy after the HTTPRoute is created"""
14+
for policy in [rate_limit, override_merge_rate_limit]: # Forcing order of creation.
15+
request.addfinalizer(policy.delete)
16+
policy.commit()
17+
policy.wait_for_accepted()
18+
19+
20+
def test_multiple_policies_merge_default_ab(client, rate_limit, override_merge_rate_limit):
21+
"""Test RateLimitPolicy with merge overrides always being enforced"""
22+
assert rate_limit.wait_until(
23+
has_condition(
24+
"Enforced",
25+
"False",
26+
"Overridden",
27+
"RateLimitPolicy is overridden by "
28+
f"[{override_merge_rate_limit.namespace()}/{override_merge_rate_limit.name()}]",
29+
)
30+
)
31+
32+
responses = client.get_many("/get", OVERRIDE_LIMIT.limit)
33+
responses.assert_all(status_code=200)
34+
assert client.get("/get").status_code == 429
35+
36+
responses = client.get_many("/anything", OVERRIDE_LIMIT2.limit)
37+
responses.assert_all(status_code=200)
38+
assert client.get("/anything").status_code == 429
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
"""Test override policy aimed at the same resource always takes precedence."""
2+
3+
import pytest
4+
5+
from testsuite.kuadrant.policy import has_condition
6+
from .conftest import OVERRIDE_LIMIT, OVERRIDE_LIMIT2
7+
8+
pytestmark = [pytest.mark.kuadrant_only, pytest.mark.limitador]
9+
10+
11+
@pytest.fixture(scope="module", autouse=True)
12+
def commit(request, route, rate_limit, override_merge_rate_limit): # pylint: disable=unused-argument
13+
"""Commits RateLimitPolicy after the HTTPRoute is created"""
14+
for policy in [override_merge_rate_limit, rate_limit]: # Forcing order of creation.
15+
request.addfinalizer(policy.delete)
16+
policy.commit()
17+
policy.wait_for_accepted()
18+
19+
20+
def test_multiple_policies_merge_default_ba(client, rate_limit, override_merge_rate_limit):
21+
"""Test RateLimitPolicy with merge overrides always being enforced"""
22+
assert rate_limit.wait_until(
23+
has_condition(
24+
"Enforced",
25+
"False",
26+
"Overridden",
27+
"RateLimitPolicy is overridden by "
28+
f"[{override_merge_rate_limit.namespace()}/{override_merge_rate_limit.name()}]",
29+
)
30+
)
31+
32+
responses = client.get_many("/get", OVERRIDE_LIMIT.limit)
33+
responses.assert_all(status_code=200)
34+
assert client.get("/get").status_code == 429
35+
36+
responses = client.get_many("/anything", OVERRIDE_LIMIT2.limit)
37+
responses.assert_all(status_code=200)
38+
assert client.get("/anything").status_code == 429

0 commit comments

Comments
 (0)