Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add identical hostnames test for RateLimitPolicy #462

Merged
merged 1 commit into from
Jul 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 16 additions & 3 deletions testsuite/policy/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,24 @@ class Policy(OpenShiftObject):
"""Base class with common functionality for all policies"""

def wait_for_ready(self):
"""Wait for a Policy to be Enforced"""
success = self.wait_until(has_condition("Enforced", "True"))
assert success, f"{self.kind()} did not get ready in time"
"""Wait for a Policy to be ready"""
self.wait_for_full_enforced()

def wait_for_accepted(self):
"""Wait for a Policy to be Accepted"""
success = self.wait_until(has_condition("Accepted", "True"))
assert success, f"{self.kind()} did not get accepted in time"

def wait_for_partial_enforced(self):
"""Wait for a Policy to be partially Enforced"""
success = self.wait_until(
has_condition("Enforced", "True", "Enforced", f"{self.kind(False)} has been partially enforced")
)
assert success, f"{self.kind(False)} did not get partially enforced in time"

def wait_for_full_enforced(self):
"""Wait for a Policy to be fully Enforced"""
success = self.wait_until(
has_condition("Enforced", "True", "Enforced", f"{self.kind(False)} has been successfully enforced")
)
assert success, f"{self.kind()} did not get fully enforced in time"
18 changes: 10 additions & 8 deletions testsuite/tests/kuadrant/identical_hostnames/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,20 @@
from testsuite.gateway.gateway_api.route import HTTPRoute


@pytest.fixture(scope="module")
def route(route, backend):
"""Adding /anything/route1 prefix to the backend"""
route.remove_all_backend()
route.add_backend(backend, "/anything/route1")
return route


@pytest.fixture(scope="module", autouse=True)
def route2(request, gateway, blame, hostname, backend, module_label) -> GatewayRoute:
"""HTTPRoute object serving as a 2nd route declaring identical hostname but different path"""
route = HTTPRoute.create_instance(gateway.openshift, blame("route"), gateway, {"app": module_label})
route = HTTPRoute.create_instance(gateway.openshift, blame("route2"), gateway, {"app": module_label})
route.add_hostname(hostname.hostname)
route.add_backend(backend, "/anything/")
route.add_backend(backend, "/anything/route2")
request.addfinalizer(route.delete)
route.commit()
return route


@pytest.fixture(scope="module")
def authorization_name2(blame):
"""Name of the 2nd Authorization resource"""
return blame("authz2")
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@


@pytest.fixture(scope="class", autouse=True)
def authorization2(request, gateway, authorization_name2, openshift, label):
def authorization2(request, gateway, blame, openshift, label):
"""2nd Authorization object"""
auth_policy = AuthPolicy.create_instance(openshift, authorization_name2, gateway, labels={"testRun": label})
auth_policy = AuthPolicy.create_instance(openshift, blame("authz2"), gateway, labels={"testRun": label})
auth_policy.authorization.add_opa_policy("rego", "allow = false")
request.addfinalizer(auth_policy.delete)
auth_policy.commit()
Expand All @@ -27,8 +27,8 @@ def test_identical_hostnames_auth_on_gw_and_route_ignored(client, authorization,
Tests that Gateway-attached AuthPolicy is ignored on 'route2' if both 'route' and 'route2' declare
identical hostname and there is another AuthPolicy already successfully enforced on 'route'.
Setup:
- Two HTTPRoutes declaring identical hostnames but different paths ('/' and '/anything/')
- Empty AuthPolicy enforced on the '/' HTTPRoute
- Two HTTPRoutes declaring identical hostnames but different paths: '/anything/route1' and '/anything/route2'
- Empty AuthPolicy enforced on the '/anything/route1' HTTPRoute
- 'deny-all' AuthPolicy (created after Empty AuthPolicy) enforced on the Gateway
Test:
- Send a request via 'route' and assert that response status code is 200
Expand All @@ -42,20 +42,20 @@ def test_identical_hostnames_auth_on_gw_and_route_ignored(client, authorization,
authorization.wait_for_ready()

# Access via 'route' is allowed due to Empty AuthPolicy
response = client.get("/get")
response = client.get("/anything/route1/get")
assert response.status_code == 200

# Despite 'deny-all' Gateway AuthPolicy reporting being successfully enforced
# it is still allowed to access the resources via 'route2'
response = client.get("/anything/get")
response = client.get("/anything/route2/get")
assert response.status_code == 200

# Deletion of Empty AuthPolicy should make the 'deny-all' Gateway AuthPolicy effectively enforced on both routes.
# It might take some time hence the use of retry client.
authorization.delete()
with hostname.client(retry_codes={200}) as retry_client:
response = retry_client.get("/get")
response = retry_client.get("/anything/route1/get")
assert response.status_code == 403

response = client.get("/anything/get")
response = client.get("/anything/route2/get")
assert response.status_code == 403
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@


@pytest.fixture(scope="class")
def authorization2(request, route2, authorization_name2, openshift, label):
def authorization2(request, route2, blame, openshift, label):
"""2nd Authorization object"""
auth_policy = AuthPolicy.create_instance(openshift, authorization_name2, route2, labels={"testRun": label})
auth_policy = AuthPolicy.create_instance(openshift, blame("authz2"), route2, labels={"testRun": label})
auth_policy.authorization.add_opa_policy("rego", "allow = false")
request.addfinalizer(auth_policy.delete)
auth_policy.commit()
Expand All @@ -28,9 +28,9 @@ def test_identical_hostnames_auth_on_routes_rejected(client, authorization, auth
Tests that 2nd AuthPolicy is rejected on 'route2' declaring identical hostname as 'route' with another
AuthPolicy already successfully enforced on 'route'.
Setup:
- Two HTTPRoutes declaring identical hostnames but different paths ('/' and '/anything/')
- Empty AuthPolicy enforced on the '/' HTTPRoute
- 'deny-all' AuthPolicy (created after Empty AuthPolicy) accepted on the '/anything/' HTTPRoute
- Two HTTPRoutes declaring identical hostnames but different paths: '/anything/route1' and '/anything/route2'
- Empty AuthPolicy enforced on the '/anything/route1' HTTPRoute
- 'deny-all' AuthPolicy (created after Empty AuthPolicy) accepted on the '/anything/route2' HTTPRoute
Test:
- Assert that 'deny-all' AuthPolicy reports an error
- Send a request via 'route' and assert that response status code is 200
Expand All @@ -54,10 +54,10 @@ def test_identical_hostnames_auth_on_routes_rejected(client, authorization, auth
f"instead it was: {authorization2.refresh().model.status.conditions}"
)

response = client.get("/get")
response = client.get("/anything/route1/get")
assert response.status_code == 200

response = client.get("/anything/get")
response = client.get("/anything/route2/get")
assert response.status_code == 200

# Deletion of Empty AuthPolicy should allow for 'deny-all' AuthPolicy to be enforced successfully.
Expand All @@ -71,9 +71,9 @@ def test_identical_hostnames_auth_on_routes_rejected(client, authorization, auth
authorization2.wait_for_ready()

# Access via 'route' is still allowed
response = client.get("/get")
response = client.get("/anything/route1/get")
assert response.status_code == 200

# Access via 'route2' is now not allowed due to 'deny-all' AuthPolicy being enforced on 'route2'
response = client.get("/anything/get")
response = client.get("/anything/route2/get")
assert response.status_code == 403
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
"""
Tests behavior of using one HTTPRoute declaring the same hostname as parent Gateway related to RateLimitPolicy.
https://github.com/Kuadrant/kuadrant-operator/blob/main/doc/rate-limiting.md#limitation-multiple-network-resources-with-identical-hostnames
(second topology mentioned there)
"""

import pytest

from testsuite.policy.rate_limit_policy import RateLimitPolicy, Limit

pytestmark = [pytest.mark.kuadrant_only]


@pytest.fixture(scope="class")
def rate_limit2(request, gateway, blame, openshift, label):
"""2nd RateLimitPolicy object allowing 1 request per 10 minutes (a.k.a. '1rp10m')"""
rlp = RateLimitPolicy.create_instance(openshift, blame("2rp10m"), gateway, labels={"testRun": label})
request.addfinalizer(rlp.delete)
rlp.add_limit("1rp10m", [Limit(1, 600)])
rlp.commit()
rlp.wait_for_partial_enforced()
return rlp


def test_identical_hostnames_rlp_on_gw_and_route_ignored(client, rate_limit, rate_limit2, hostname):
"""
Tests that Gateway-attached RateLimitPolicy is ignored on 'route2' if both 'route' and 'route2' declare
identical hostname and there is another RateLimitPolicy already successfully enforced on 'route'.
Setup:
- Two HTTPRoutes declaring identical hostnames but different paths: '/anything/route1' and '/anything/route2'
- Empty RateLimitPolicy enforced on the '/anything/route1' HTTPRoute
- '1rp10m' RateLimitPolicy (created after Empty RateLimitPolicy) enforced on the Gateway
Test:
- Send a request via 'route' and assert that no 429s (Too Many Requests) are returned
- Send a request via 'route2' and assert that no 429s (Too Many Requests)are returned
- Delete the Empty RateLimitPolicy
- Send a request via both routes
- Assert that on both routes the 429s are returned after single 200 (OK)
"""

# Verify that the Empty RLP is still enforced despite '1rp10m' RLP being enforced too now
rate_limit.wait_for_ready()

# Access via 'route' is not limited due to Empty RateLimitPolicy
responses = client.get_many("/anything/route1/get", 2)
responses.assert_all(status_code=200)

# Access via 'route2' is limited due to '1rp10m' Gateway RateLimitPolicy ( it is partially enforced)
response = client.get("/anything/route2/get")
assert response.status_code == 200
responses = client.get_many("/anything/route2/get", 2)
responses.assert_all(status_code=429)

# Deletion of Empty RateLimitPolicy should make the '1rp10m' Gateway RateLimitPolicy effectively enforced on both
# routes. It might take some time hence the use of retry client.
rate_limit.delete()
rate_limit2.wait_for_ready()
with hostname.client(retry_codes={200}) as retry_client:
response = retry_client.get("/anything/route1/get")
assert response.status_code == 429

response = client.get("/anything/route2/get")
assert response.status_code == 429
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
"""
Tests behavior of using one HTTPRoute declaring the same hostname as parent Gateway related to RateLimitPolicy.
https://github.com/Kuadrant/kuadrant-operator/blob/main/doc/rate-limiting.md#limitation-multiple-network-resources-with-identical-hostnames
(the first topology mentioned there)
"""

import pytest

from testsuite.policy.rate_limit_policy import RateLimitPolicy, Limit

pytestmark = [pytest.mark.kuadrant_only]


@pytest.fixture(scope="module")
def rate_limit(rate_limit):
"""Add limit to 1st RateLimitPolicy allowing 1 request per 10 minutes (a.k.a. '1rp10m' RateLimitPolicy)"""
rate_limit.add_limit("1rp10m", [Limit(1, 10)])
return rate_limit


@pytest.fixture(scope="class")
def rate_limit2(request, route2, blame, openshift, label):
"""2nd RateLimitPolicy allowing 2 requests per 10 minutes (a.k.a. '2rp10m' RateLimitPolicy)"""
rlp = RateLimitPolicy.create_instance(openshift, blame("2rp10m"), route2, labels={"testRun": label})
request.addfinalizer(rlp.delete)
rlp.add_limit("2rp10m", [Limit(2, 10)])
rlp.commit()
rlp.wait_for_ready()
return rlp


def test_identical_hostnames_rlp_on_routes_ignored(client, rate_limit, rate_limit2, hostname):
"""
Tests that 1st RateLimitPolicy gets ignored on 'route' declaring identical hostname as 'route2' when another
RateLimitPolicy gets successfully enforced on 'route2'.
Setup:
- Two HTTPRoutes declaring identical hostnames but different paths: '/anything/route1' and '/anything/route2'
- '1rp10m' RateLimitPolicy enforced on the '/anything/route1' HTTPRoute
- '2rp10m' RateLimitPolicy (created after '1rp10m' RateLimitPolicy) enforced on the '/anything/route2' HTTPRoute
Test:
- Send a request via 'route' and assert that no 429s (Too Many Requests) are returned
- Send a request via 'route2' and assert that no 429s (Too Many Requests)are returned
- Delete the Empty RateLimitPolicy
- Send a request via both routes
- Assert that on both routes the 429s are returned after single 200 (OK)
"""

# Verify that the '1rp10m' RLP is still enforced despite '2rp10m' RLP being enforced too now
rate_limit.wait_for_ready()

# Access via 'route' is not limited at all because '1rp10m' RateLimitPolicy is ignored
# despite it reporting being successfully enforced
responses = client.get_many("/anything/route1/get", 3)
responses.assert_all(status_code=200)

# Access via 'route2' is limited due to '2rp10m' RateLimitPolicy
responses = client.get_many("/anything/route2/get", 2)
responses.assert_all(status_code=200)
# There might be more than two 200 OKs responses, might be due to '2rp10m' enforcement still being in progres
with hostname.client(retry_codes={200}) as retry_client:
response = retry_client.get("/anything/route2/get")
assert response.status_code == 429

# Deletion of '2rp10m' RateLimitPolicy should make '1rp10m' RateLimitPolicy effectively enforced.
rate_limit2.delete()
rate_limit.wait_for_ready()

# Access via 'route' should now be limited via '1rp10m' RateLimitPolicy
response = client.get("/anything/route1/get")
assert response.status_code == 200
# There might be more than two 200 OKs responses, might be due to '1rp10m' enforcement still being in progres
with hostname.client(retry_codes={200}) as retry_client:
response = retry_client.get("/anything/route1/get")
assert response.status_code == 429

# Access via 'route2' is now not limited at all
responses = client.get_many("/anything/route2/get", 3)
responses.assert_all(status_code=200)
Loading