From 961870d0ef979a76d1e6d232d4c360e904c893c7 Mon Sep 17 00:00:00 2001 From: averevki Date: Tue, 18 Feb 2025 19:55:37 +0100 Subject: [PATCH] Add tests for D&O merge strategy Signed-off-by: averevki --- testsuite/kuadrant/policy/__init__.py | 8 +++ .../policy/authorization/auth_policy.py | 14 ++++- testsuite/kuadrant/policy/rate_limit.py | 24 +++++++-- .../singlecluster/defaults/merge/__init__.py | 0 .../singlecluster/defaults/merge/conftest.py | 12 +++++ .../defaults/merge/same_target/__init__.py | 0 .../defaults/merge/same_target/conftest.py | 28 ++++++++++ .../merge/same_target/test_ab_strategy.py | 27 ++++++++++ .../merge/same_target/test_ba_startegy.py | 27 ++++++++++ .../merge/test_gateway_default_merge.py | 51 +++++++++++++++++++ .../singlecluster/overrides/merge/__init__.py | 0 .../singlecluster/overrides/merge/conftest.py | 12 +++++ .../overrides/merge/same_target/__init__.py | 0 .../overrides/merge/same_target/conftest.py | 28 ++++++++++ .../merge/same_target/test_ab_strategy.py | 27 ++++++++++ .../merge/same_target/test_ba_startegy.py | 27 ++++++++++ .../merge/test_gateway_override_merge.py | 51 +++++++++++++++++++ 17 files changed, 332 insertions(+), 4 deletions(-) create mode 100644 testsuite/tests/singlecluster/defaults/merge/__init__.py create mode 100644 testsuite/tests/singlecluster/defaults/merge/conftest.py create mode 100644 testsuite/tests/singlecluster/defaults/merge/same_target/__init__.py create mode 100644 testsuite/tests/singlecluster/defaults/merge/same_target/conftest.py create mode 100644 testsuite/tests/singlecluster/defaults/merge/same_target/test_ab_strategy.py create mode 100644 testsuite/tests/singlecluster/defaults/merge/same_target/test_ba_startegy.py create mode 100644 testsuite/tests/singlecluster/defaults/merge/test_gateway_default_merge.py create mode 100644 testsuite/tests/singlecluster/overrides/merge/__init__.py create mode 100644 testsuite/tests/singlecluster/overrides/merge/conftest.py create mode 100644 testsuite/tests/singlecluster/overrides/merge/same_target/__init__.py create mode 100644 testsuite/tests/singlecluster/overrides/merge/same_target/conftest.py create mode 100644 testsuite/tests/singlecluster/overrides/merge/same_target/test_ab_strategy.py create mode 100644 testsuite/tests/singlecluster/overrides/merge/same_target/test_ba_startegy.py create mode 100644 testsuite/tests/singlecluster/overrides/merge/test_gateway_override_merge.py diff --git a/testsuite/kuadrant/policy/__init__.py b/testsuite/kuadrant/policy/__init__.py index d4ba2ad8..2666af52 100644 --- a/testsuite/kuadrant/policy/__init__.py +++ b/testsuite/kuadrant/policy/__init__.py @@ -1,11 +1,19 @@ """Contains Base class for policies""" from dataclasses import dataclass +from enum import Enum from testsuite.kubernetes import KubernetesObject from testsuite.utils import check_condition +class Strategy(Enum): + """Class for merge strategies of defaults and overrides.""" + + ATOMIC = "atomic" + MERGE = "merge" + + @dataclass class CelPredicate: """Dataclass that references CEL predicate e.g. auth.identity.anonymous == 'true'""" diff --git a/testsuite/kuadrant/policy/authorization/auth_policy.py b/testsuite/kuadrant/policy/authorization/auth_policy.py index 4c77bfe4..c394b7dd 100644 --- a/testsuite/kuadrant/policy/authorization/auth_policy.py +++ b/testsuite/kuadrant/policy/authorization/auth_policy.py @@ -9,7 +9,7 @@ from testsuite.utils import asdict from .auth_config import AuthConfig from .sections import ResponseSection -from .. import Policy, CelPredicate +from .. import Policy, CelPredicate, Strategy from . import Pattern @@ -27,6 +27,7 @@ def create_instance( name, target: Referencable, labels: Dict[str, str] = None, + section_name: str = None, ): """Creates base instance""" model: Dict = { @@ -37,6 +38,8 @@ def create_instance( "targetRef": target.reference, }, } + if section_name: + model["spec"]["targetRef"]["sectionName"] = section_name return cls(model, context=cluster.context) @@ -46,6 +49,15 @@ def add_rule(self, when: list[CelPredicate]): self.model.spec.setdefault("when", []) self.model.spec["when"].extend([asdict(x) for x in when]) + @modify + def strategy(self, strategy: Strategy) -> None: + """Add strategy type to default or overrides spec""" + if self.spec_section is None: + raise TypeError("Strategy can only be set on defaults or overrides") + + self.spec_section["strategy"] = strategy.value + self.spec_section = None + @property def auth_section(self): if self.spec_section is None: diff --git a/testsuite/kuadrant/policy/rate_limit.py b/testsuite/kuadrant/policy/rate_limit.py index a46ee1ea..7c11d859 100644 --- a/testsuite/kuadrant/policy/rate_limit.py +++ b/testsuite/kuadrant/policy/rate_limit.py @@ -7,7 +7,7 @@ from testsuite.gateway import Referencable from testsuite.kubernetes import modify from testsuite.kubernetes.client import KubernetesClient -from testsuite.kuadrant.policy import Policy, CelPredicate, CelExpression +from testsuite.kuadrant.policy import Policy, CelPredicate, CelExpression, Strategy from testsuite.utils import asdict @@ -27,9 +27,16 @@ def __init__(self, *args, **kwargs): self.spec_section = None @classmethod - def create_instance(cls, cluster: KubernetesClient, name, target: Referencable, labels: dict[str, str] = None): + def create_instance( + cls, + cluster: KubernetesClient, + name, + target: Referencable, + section_name: str = None, + labels: dict[str, str] = None, + ): """Creates new instance of RateLimitPolicy""" - model = { + model: dict = { "apiVersion": "kuadrant.io/v1", "kind": "RateLimitPolicy", "metadata": {"name": name, "labels": labels}, @@ -37,6 +44,8 @@ def create_instance(cls, cluster: KubernetesClient, name, target: Referencable, "targetRef": target.reference, }, } + if section_name: + model["spec"]["targetRef"]["sectionName"] = section_name return cls(model, context=cluster.context) @@ -63,6 +72,15 @@ def add_limit( self.spec_section.setdefault("limits", {})[name] = limit self.spec_section = None + @modify + def strategy(self, strategy: Strategy) -> None: + """Add strategy type to default or overrides spec""" + if self.spec_section is None: + raise TypeError("Strategy can only be set on defaults or overrides") + + self.spec_section["strategy"] = strategy.value + self.spec_section = None + @property def defaults(self): """Add new rule into the `defaults` RateLimitPolicy section""" diff --git a/testsuite/tests/singlecluster/defaults/merge/__init__.py b/testsuite/tests/singlecluster/defaults/merge/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/testsuite/tests/singlecluster/defaults/merge/conftest.py b/testsuite/tests/singlecluster/defaults/merge/conftest.py new file mode 100644 index 00000000..ce759af6 --- /dev/null +++ b/testsuite/tests/singlecluster/defaults/merge/conftest.py @@ -0,0 +1,12 @@ +"""Conftest for defaults merge strategy tests""" + +import pytest + + +@pytest.fixture(scope="module") +def route(route, backend): + """Add 2 backend rules for specific backend paths""" + route.remove_all_rules() + route.add_backend(backend, "/get") + route.add_backend(backend, "/anything") + return route diff --git a/testsuite/tests/singlecluster/defaults/merge/same_target/__init__.py b/testsuite/tests/singlecluster/defaults/merge/same_target/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/testsuite/tests/singlecluster/defaults/merge/same_target/conftest.py b/testsuite/tests/singlecluster/defaults/merge/same_target/conftest.py new file mode 100644 index 00000000..f5cdf0d7 --- /dev/null +++ b/testsuite/tests/singlecluster/defaults/merge/same_target/conftest.py @@ -0,0 +1,28 @@ +"""Setup conftest for policy merge on the same targets""" + +import pytest + +from testsuite.kuadrant.policy import CelPredicate, Strategy +from testsuite.kuadrant.policy.rate_limit import Limit, RateLimitPolicy + +LIMIT = Limit(4, "10s") +MERGE_LIMIT = Limit(2, "10s") +MERGE_LIMIT2 = Limit(6, "10s") + + +@pytest.fixture(scope="module") +def rate_limit(cluster, blame, module_label, route): + """Add a RateLimitPolicy targeting the first HTTPRouteRule.""" + rate_limit = RateLimitPolicy.create_instance(cluster, blame("sp"), route, labels={"testRun": module_label}) + rate_limit.add_limit("basic", [LIMIT], when=[CelPredicate("request.path == '/get'")]) + return rate_limit + + +@pytest.fixture(scope="module") +def default_merge_rate_limit(cluster, blame, module_label, route): + """Add a RateLimitPolicy targeting the first HTTPRouteRule.""" + policy = RateLimitPolicy.create_instance(cluster, blame("dmp"), route, labels={"testRun": module_label}) + policy.defaults.add_limit("basic", [MERGE_LIMIT], when=[CelPredicate("request.path == '/get'")]) + policy.defaults.add_limit("merge", [MERGE_LIMIT2], when=[CelPredicate("request.path == '/anything'")]) + policy.defaults.strategy(Strategy.MERGE) + return policy diff --git a/testsuite/tests/singlecluster/defaults/merge/same_target/test_ab_strategy.py b/testsuite/tests/singlecluster/defaults/merge/same_target/test_ab_strategy.py new file mode 100644 index 00000000..9a7aafe2 --- /dev/null +++ b/testsuite/tests/singlecluster/defaults/merge/same_target/test_ab_strategy.py @@ -0,0 +1,27 @@ +"""Test defaults policy aimed at the same resoure uses oldested policy.""" + +import pytest + +from .conftest import MERGE_LIMIT, MERGE_LIMIT2 + +pytestmark = [pytest.mark.kuadrant_only, pytest.mark.limitador] + + +@pytest.fixture(scope="module", autouse=True) +def commit(request, route, rate_limit, default_merge_rate_limit): # pylint: disable=unused-argument + """Commits RateLimitPolicy after the HTTPRoute is created""" + for policy in [rate_limit, default_merge_rate_limit]: # Forcing order of creation. + request.addfinalizer(policy.delete) + policy.commit() + policy.wait_for_accepted() + + +def test_multiple_policies_merge_default_ab(client): + """Test RateLimitPolicy with merge defaults being ignored due to age""" + responses = client.get_many("/get", MERGE_LIMIT.limit) + responses.assert_all(status_code=200) + assert client.get("/get").status_code == 429 + + responses = client.get_many("/anything", MERGE_LIMIT2.limit) + responses.assert_all(status_code=200) + assert client.get("/anything").status_code == 429 diff --git a/testsuite/tests/singlecluster/defaults/merge/same_target/test_ba_startegy.py b/testsuite/tests/singlecluster/defaults/merge/same_target/test_ba_startegy.py new file mode 100644 index 00000000..e5bac4a8 --- /dev/null +++ b/testsuite/tests/singlecluster/defaults/merge/same_target/test_ba_startegy.py @@ -0,0 +1,27 @@ +"""Test defaults policy aimed at the same resoure uses oldested policy.""" + +import pytest + +from .conftest import LIMIT, MERGE_LIMIT2 + +pytestmark = [pytest.mark.kuadrant_only, pytest.mark.limitador] + + +@pytest.fixture(scope="module", autouse=True) +def commit(request, route, rate_limit, default_merge_rate_limit): # pylint: disable=unused-argument + """Commits RateLimitPolicy after the HTTPRoute is created""" + for policy in [default_merge_rate_limit, rate_limit]: # Forcing order of creation. + request.addfinalizer(policy.delete) + policy.commit() + policy.wait_for_accepted() + + +def test_multiple_policies_merge_default_ba(client): + """Test RateLimitPolicy with merge defaults being enforced due to age""" + responses = client.get_many("/get", LIMIT.limit) + responses.assert_all(status_code=200) + assert client.get("/get").status_code == 429 + + responses = client.get_many("/anything", MERGE_LIMIT2.limit) + responses.assert_all(status_code=200) + assert client.get("/anything").status_code == 429 diff --git a/testsuite/tests/singlecluster/defaults/merge/test_gateway_default_merge.py b/testsuite/tests/singlecluster/defaults/merge/test_gateway_default_merge.py new file mode 100644 index 00000000..6c524040 --- /dev/null +++ b/testsuite/tests/singlecluster/defaults/merge/test_gateway_default_merge.py @@ -0,0 +1,51 @@ +"""Test gateway level default merging with and being patrially overriden by another policy.""" + +import pytest + +from testsuite.kuadrant.policy import CelPredicate +from testsuite.kuadrant.policy.rate_limit import RateLimitPolicy, Limit, Strategy + +pytestmark = [pytest.mark.kuadrant_only, pytest.mark.limitador] + + +@pytest.fixture(scope="module") +def rate_limit(rate_limit): + """Create a RateLimitPolicy with a basic limit with same target as one default.""" + when = CelPredicate("request.path == '/get'") + rate_limit.add_limit("route_limit", [Limit(5, "5s")], when=[when]) + return rate_limit + + +@pytest.fixture(scope="module") +def global_rate_limit(cluster, blame, module_label, gateway): + """Create a RateLimitPolicy with default policies and a merge strategy.""" + global_rate_limit = RateLimitPolicy.create_instance( + cluster, blame("limit"), gateway, labels={"testRun": module_label} + ) + gateway_when = CelPredicate("request.path == '/anything'") + global_rate_limit.defaults.add_limit("gateway_limit", [Limit(3, "5s")], when=[gateway_when]) + route_when = CelPredicate("request.path == '/get'") + global_rate_limit.defaults.add_limit("route_limit", [Limit(10, "5s")], when=[route_when]) + global_rate_limit.defaults.strategy(Strategy.MERGE) + return global_rate_limit + + +@pytest.fixture(scope="module", autouse=True) +def commit(request, route, rate_limit, global_rate_limit): # pylint: disable=unused-argument + """Commits RateLimitPolicy after the HTTPRoute is created""" + for policy in [global_rate_limit, rate_limit]: # Forcing order of creation. + request.addfinalizer(policy.delete) + policy.commit() + policy.wait_for_ready() + + +@pytest.mark.parametrize("rate_limit", ["gateway", "route"], indirect=True) +def test_gateway_default_merge(client): + """Test Gateway default policy being partially overriden when another policy with the same target is created.""" + get = client.get_many("/get", 5) + get.assert_all(status_code=200) + assert client.get("/get").status_code == 429 + + anything = client.get_many("/anything", 3) + anything.assert_all(status_code=200) + assert client.get("/anything").status_code == 429 diff --git a/testsuite/tests/singlecluster/overrides/merge/__init__.py b/testsuite/tests/singlecluster/overrides/merge/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/testsuite/tests/singlecluster/overrides/merge/conftest.py b/testsuite/tests/singlecluster/overrides/merge/conftest.py new file mode 100644 index 00000000..d66b92a9 --- /dev/null +++ b/testsuite/tests/singlecluster/overrides/merge/conftest.py @@ -0,0 +1,12 @@ +"""Conftest for overrides merge strategy tests""" + +import pytest + + +@pytest.fixture(scope="module") +def route(route, backend): + """Add 2 backend rules for specific backend paths""" + route.remove_all_rules() + route.add_backend(backend, "/get") + route.add_backend(backend, "/anything") + return route diff --git a/testsuite/tests/singlecluster/overrides/merge/same_target/__init__.py b/testsuite/tests/singlecluster/overrides/merge/same_target/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/testsuite/tests/singlecluster/overrides/merge/same_target/conftest.py b/testsuite/tests/singlecluster/overrides/merge/same_target/conftest.py new file mode 100644 index 00000000..c1b47098 --- /dev/null +++ b/testsuite/tests/singlecluster/overrides/merge/same_target/conftest.py @@ -0,0 +1,28 @@ +"""Setup conftest for policy override on the same targets""" + +import pytest + +from testsuite.kuadrant.policy import CelPredicate, Strategy +from testsuite.kuadrant.policy.rate_limit import Limit, RateLimitPolicy + +LIMIT = Limit(4, "5s") +OVERRIDE_LIMIT = Limit(6, "5s") +OVERRIDE_LIMIT2 = Limit(2, "5s") + + +@pytest.fixture(scope="module") +def rate_limit(cluster, blame, module_label, route): + """Add a RateLimitPolicy targeting the first HTTPRouteRule.""" + rate_limit = RateLimitPolicy.create_instance(cluster, blame("sp"), route, labels={"testRun": module_label}) + rate_limit.add_limit("basic", [LIMIT], when=[CelPredicate("request.path == '/get'")]) + return rate_limit + + +@pytest.fixture(scope="module") +def override_merge_rate_limit(cluster, blame, module_label, route): + """Add a RateLimitPolicy targeting the first HTTPRouteRule.""" + policy = RateLimitPolicy.create_instance(cluster, blame("omp"), route, labels={"testRun": module_label}) + policy.overrides.add_limit("basic", [OVERRIDE_LIMIT], when=[CelPredicate("request.path == '/get'")]) + policy.overrides.add_limit("override", [OVERRIDE_LIMIT2], when=[CelPredicate("request.path == '/anything'")]) + policy.overrides.strategy(Strategy.MERGE) + return policy diff --git a/testsuite/tests/singlecluster/overrides/merge/same_target/test_ab_strategy.py b/testsuite/tests/singlecluster/overrides/merge/same_target/test_ab_strategy.py new file mode 100644 index 00000000..c329e3dc --- /dev/null +++ b/testsuite/tests/singlecluster/overrides/merge/same_target/test_ab_strategy.py @@ -0,0 +1,27 @@ +"""Test override policies aimed at the same resoure uses oldested policy.""" + +import pytest + +from .conftest import OVERRIDE_LIMIT, OVERRIDE_LIMIT2 + +pytestmark = [pytest.mark.kuadrant_only, pytest.mark.limitador] + + +@pytest.fixture(scope="module", autouse=True) +def commit(request, route, rate_limit, override_merge_rate_limit): # pylint: disable=unused-argument + """Commits RateLimitPolicy after the HTTPRoute is created""" + for policy in [rate_limit, override_merge_rate_limit]: # Forcing order of creation. + request.addfinalizer(policy.delete) + policy.commit() + policy.wait_for_accepted() + + +def test_multiple_policies_merge_default_ab(client): + """Test RateLimitPolicy with merge overrides being ignored due to age""" + responses = client.get_many("/get", OVERRIDE_LIMIT.limit) + responses.assert_all(status_code=200) + assert client.get("/get").status_code == 429 + + responses = client.get_many("/anything", OVERRIDE_LIMIT2.limit) + responses.assert_all(status_code=200) + assert client.get("/anything").status_code == 429 diff --git a/testsuite/tests/singlecluster/overrides/merge/same_target/test_ba_startegy.py b/testsuite/tests/singlecluster/overrides/merge/same_target/test_ba_startegy.py new file mode 100644 index 00000000..1bea16a3 --- /dev/null +++ b/testsuite/tests/singlecluster/overrides/merge/same_target/test_ba_startegy.py @@ -0,0 +1,27 @@ +"""Test override policy aimed at the same resoure uses oldested policy.""" + +import pytest + +from .conftest import OVERRIDE_LIMIT, OVERRIDE_LIMIT2 + +pytestmark = [pytest.mark.kuadrant_only, pytest.mark.limitador] + + +@pytest.fixture(scope="module", autouse=True) +def commit(request, route, rate_limit, override_merge_rate_limit): # pylint: disable=unused-argument + """Commits RateLimitPolicy after the HTTPRoute is created""" + for policy in [override_merge_rate_limit, rate_limit]: # Forcing order of creation. + request.addfinalizer(policy.delete) + policy.commit() + policy.wait_for_accepted() + + +def test_multiple_policies_merge_default_ba(client): + """Test RateLimitPolicy with merge overrides being enforced due to age""" + responses = client.get_many("/get", OVERRIDE_LIMIT.limit) + responses.assert_all(status_code=200) + assert client.get("/get").status_code == 429 + + responses = client.get_many("/anything", OVERRIDE_LIMIT2.limit) + responses.assert_all(status_code=200) + assert client.get("/anything").status_code == 429 diff --git a/testsuite/tests/singlecluster/overrides/merge/test_gateway_override_merge.py b/testsuite/tests/singlecluster/overrides/merge/test_gateway_override_merge.py new file mode 100644 index 00000000..9d7110c4 --- /dev/null +++ b/testsuite/tests/singlecluster/overrides/merge/test_gateway_override_merge.py @@ -0,0 +1,51 @@ +"""Test override overriding another policy aimed at the same Gateway Listener.""" + +import pytest + +from testsuite.kuadrant.policy import CelPredicate +from testsuite.kuadrant.policy.rate_limit import RateLimitPolicy, Limit, Strategy + +pytestmark = [pytest.mark.kuadrant_only, pytest.mark.limitador] + + +@pytest.fixture(scope="function") +def override_rate_limit(cluster, blame, module_label, gateway): + """Add a RateLimitPolicy with a merge strategy override targeting a specific endpoint.""" + override_rate_limit = RateLimitPolicy.create_instance( + cluster, blame("limit"), gateway, labels={"testRun": module_label} + ) + when = CelPredicate("request.path == '/get'") + override_rate_limit.overrides.add_limit("route_limit", [Limit(5, "5s")], when=[when]) + override_rate_limit.overrides.strategy(Strategy.MERGE) + return override_rate_limit + + +@pytest.fixture(scope="module") +def rate_limit(rate_limit): + """Add limits targeted at specific endpoints to the RateLimitPolicy.""" + gateway_when = CelPredicate("request.path == '/anything'") + rate_limit.add_limit("gateway_limit", [Limit(3, "5s")], when=[gateway_when]) + route_when = CelPredicate("request.path == '/get'") + rate_limit.add_limit("route_limit", [Limit(10, "5s")], when=[route_when]) + return rate_limit + + +@pytest.fixture(scope="function", autouse=True) +def commit(request, route, rate_limit, override_rate_limit): # pylint: disable=unused-argument + """Commits RateLimitPolicy after the HTTPRoute is created""" + for policy in [override_rate_limit, rate_limit]: # Forcing order of creation. + request.addfinalizer(policy.delete) + policy.commit() + policy.wait_for_accepted() + + +@pytest.mark.parametrize("rate_limit", ["gateway", "route"], indirect=True) +def test_gateway_override_merge(client): + """Test RateLimitPolicy with an override and merge strategy overriding only a part of a new policy.""" + get = client.get_many("/get", 5) + get.assert_all(status_code=200) + assert client.get("/get").status_code == 429 + + anything = client.get_many("/anything", 3) + anything.assert_all(status_code=200) + assert client.get("/anything").status_code == 429