Skip to content

Commit

Permalink
Add identical hostnames test for RateLimitPolicy
Browse files Browse the repository at this point in the history
  • Loading branch information
trepel committed Jun 25, 2024
1 parent 60bf09b commit cf282db
Show file tree
Hide file tree
Showing 6 changed files with 185 additions and 11 deletions.
13 changes: 11 additions & 2 deletions testsuite/policy/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,20 @@ 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"))
"""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 ready in time"

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_partially_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"
16 changes: 15 additions & 1 deletion testsuite/tests/kuadrant/identical_hostnames/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,18 @@
from testsuite.gateway.gateway_api.route import HTTPRoute


@pytest.fixture(scope="module")
def route(route, backend):
"""Adding /nothing/ prefix to the backend"""
route.remove_all_backend()
route.add_backend(backend, "/nothing/")
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/")
request.addfinalizer(route.delete)
Expand All @@ -21,3 +29,9 @@ def route2(request, gateway, blame, hostname, backend, module_label) -> GatewayR
def authorization_name2(blame):
"""Name of the 2nd Authorization resource"""
return blame("authz2")


@pytest.fixture(scope="module")
def rate_limit_policy_name2(blame):
"""Name of the 2nd RateLimitPolicy resource"""
return blame("2rp10m")
Original file line number Diff line number Diff line change
Expand Up @@ -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 ('/nothing/' and '/anything/')
- Empty AuthPolicy enforced on the '/nothing/' 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,7 +42,7 @@ 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("/nothing/get")
assert response.status_code == 200

# Despite 'deny-all' Gateway AuthPolicy reporting being successfully enforced
Expand All @@ -54,7 +54,7 @@ def test_identical_hostnames_auth_on_gw_and_route_ignored(client, authorization,
# 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("/nothing/get")
assert response.status_code == 403

response = client.get("/anything/get")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@ 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
- Two HTTPRoutes declaring identical hostnames but different paths ('/nothing/' and '/anything/')
- Empty AuthPolicy enforced on the '/nothing/' HTTPRoute
- 'deny-all' AuthPolicy (created after Empty AuthPolicy) accepted on the '/anything/' HTTPRoute
Test:
- Assert that 'deny-all' AuthPolicy reports an error
Expand All @@ -54,7 +54,7 @@ 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("/nothing/get")
assert response.status_code == 200

response = client.get("/anything/get")
Expand All @@ -71,7 +71,7 @@ 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("/nothing/get")
assert response.status_code == 200

# Access via 'route2' is now not allowed due to 'deny-all' AuthPolicy being enforced on 'route2'
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
"""
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_limit_policy2(request, gateway, rate_limit_policy_name2, openshift, label):
"""2nd RateLimitPolicy object allowing 1 request per 10 minutes (a.k.a. '1rp10m')"""
rlp = RateLimitPolicy.create_instance(openshift, rate_limit_policy_name2, gateway, labels={"testRun": label})
request.addfinalizer(rlp.delete)
rlp.add_limit("1rp10m", [Limit(1, 600)])
rlp.commit()
rlp.wait_for_partially_enforced()
return rlp


def test_identical_hostnames_rlp_on_gw_and_route_ignored(client, rate_limit, rate_limit_policy2, 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 ('/nothing/' and '/anything/')
- Empty RateLimitPolicy enforced on the '/nothing/' 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("/nothing/get", 2)
assert all(
r.status_code == 200 for r in responses
), f"Rate Limited resource unexpectedly rejected requests {responses}"

# Access via 'route2' is limited due to '1rp10m' Gateway RateLimitPolicy ( it is partially enforced)
response = client.get("/anything/get")
assert response.status_code == 200
responses = client.get_many("/anything/get", 2)
assert all(
r.status_code == 429 for r in responses
), f"Rate Limited resource unexpectedly allowed requests {responses}"

# 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_limit_policy2.wait_for_ready()
with hostname.client(retry_codes={200}) as retry_client:
response = retry_client.get("/nothing/get")
assert response.status_code == 429

response = client.get("/anything/get")
assert response.status_code == 429
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
"""
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_limit_policy2(request, route2, rate_limit_policy_name2, openshift, label):
"""2nd RateLimitPolicy allowing 2 requests per 10 minutes (a.k.a. '2rp10m' RateLimitPolicy)"""
rlp = RateLimitPolicy.create_instance(openshift, rate_limit_policy_name2, 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_limit_policy2, 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 ('/nothing/' and '/anything/')
- '1rp10m' RateLimitPolicy enforced on the '/nothing/' HTTPRoute
- '2rp10m' RateLimitPolicy (created after '1rp10m' RateLimitPolicy) enforced on the '/anything/' 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("/nothing/get", 3)
assert all(
r.status_code == 200 for r in responses
), f"Rate Limited resource unexpectedly rejected requests {responses}"

# Access via 'route2' is limited due to '2rp10m' RateLimitPolicy
response = client.get_many("/anything/get", 2)
assert all(
r.status_code == 200 for r in responses
), f"Rate Limited resource unexpectedly rejected requests {responses}"
# 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/get")
assert response.status_code == 429

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

# Access via 'route' should now be limited via '1rp10m' RateLimitPolicy
response = client.get("/nothing/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("/nothing/get")
assert response.status_code == 429

# Access via 'route2' is now not limited at all
responses = client.get_many("/anything/get", 3)
assert all(
r.status_code == 200 for r in responses
), f"Rate Limited resource unexpectedly rejected requests {responses}"

0 comments on commit cf282db

Please sign in to comment.