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 26, 2024
1 parent 60bf09b commit 6af4668
Show file tree
Hide file tree
Showing 6 changed files with 184 additions and 28 deletions.
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)

0 comments on commit 6af4668

Please sign in to comment.