Skip to content

Commit a95386b

Browse files
committed
fix: update MaaS tests for subscription binding and fix gateway 500 error
Signed-off-by: Swati Mukund Bagal <sbagal@redhat.com>
1 parent 8a67a44 commit a95386b

File tree

5 files changed

+112
-25
lines changed

5 files changed

+112
-25
lines changed

tests/model_serving/maas_billing/conftest.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -828,7 +828,10 @@ def maas_gateway_api(
828828
if gw.exists:
829829
LOGGER.info(f"Reusing existing gateway {MAAS_GATEWAY_NAMESPACE}/{MAAS_GATEWAY_NAME}")
830830
gw.wait_for_condition(condition="Programmed", status="True", timeout=300)
831-
yield
831+
with ResourceEditor(
832+
patches={gw: {"metadata": {"annotations": {"security.opendatahub.io/authorino-tls-bootstrap": "true"}}}}
833+
):
834+
yield
832835
else:
833836
LOGGER.info(f"Creating gateway {MAAS_GATEWAY_NAMESPACE}/{MAAS_GATEWAY_NAME}")
834837
with Gateway(
@@ -837,7 +840,10 @@ def maas_gateway_api(
837840
namespace=MAAS_GATEWAY_NAMESPACE,
838841
gateway_class_name="openshift-default",
839842
listeners=maas_gateway_listeners(hostname=maas_gateway_api_hostname),
840-
annotations={"opendatahub.io/managed": "false"},
843+
annotations={
844+
"opendatahub.io/managed": "false",
845+
"security.opendatahub.io/authorino-tls-bootstrap": "true",
846+
},
841847
label={
842848
"app.kubernetes.io/name": "maas",
843849
"app.kubernetes.io/instance": MAAS_GATEWAY_NAME,

tests/model_serving/maas_billing/maas_subscription/conftest.py

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import secrets
22
import string
3+
import time
34
from collections.abc import Generator
45
from contextlib import ExitStack
56
from typing import Any
@@ -388,12 +389,14 @@ def maas_api_key_for_actor(
388389
maas_subscription_controller_enabled_latest: None,
389390
maas_gateway_api: None,
390391
maas_api_gateway_reachable: None,
392+
maas_subscription_tinyllama_free: Any,
391393
) -> str:
392394
"""
393395
Create an API key for the current actor (admin/free/premium).
394396
395397
Flow:
396398
- Use OpenShift token (ocp_token_for_actor) to create an API key via MaaS API.
399+
- Bind the key to the free subscription at mint time (required for model inference).
397400
- Use the plaintext API key for gateway inference: Authorization: Bearer <sk-...>.
398401
"""
399402
api_key_name = f"odh-sub-tests-{generate_random_name()}"
@@ -404,6 +407,7 @@ def maas_api_key_for_actor(
404407
request_session_http=request_session_http,
405408
api_key_name=api_key_name,
406409
request_timeout_seconds=60,
410+
subscription=maas_subscription_tinyllama_free.name,
407411
)
408412

409413
return body["key"]
@@ -535,7 +539,7 @@ def premium_system_authenticated_access(
535539
model_namespace=maas_model_tinyllama_premium.namespace,
536540
tokens_per_minute=100,
537541
window="1m",
538-
priority=0,
542+
priority=1,
539543
teardown=True,
540544
wait_for_resource=True,
541545
) as system_authenticated_subscription,
@@ -547,6 +551,24 @@ def premium_system_authenticated_access(
547551
timeout=300,
548552
)
549553

554+
# Wait for the underlying Kuadrant AuthPolicy to be accepted and enforced.
555+
# The MaaS CR being "Ready" does not guarantee Kuadrant has propagated the update.
556+
kuadrant_wait_timeout = 600
557+
deadline = time.monotonic() + kuadrant_wait_timeout
558+
while time.monotonic() < deadline:
559+
auth_policies = (extra_auth_policy.instance.status or {}).get("authPolicies", [])
560+
if auth_policies and all(
561+
ap.get("accepted") == "True" and ap.get("enforced") == "True" for ap in auth_policies
562+
):
563+
LOGGER.info(f"Kuadrant AuthPolicy enforced for {extra_auth_policy.name}")
564+
break
565+
time.sleep(5)
566+
else:
567+
LOGGER.warning(
568+
f"Kuadrant AuthPolicy for {extra_auth_policy.name} did not become "
569+
f"enforced within {kuadrant_wait_timeout}s — proceeding anyway"
570+
)
571+
550572
LOGGER.info(
551573
f"Created extra AuthPolicy {extra_auth_policy.name} and subscription "
552574
f"{system_authenticated_subscription.name} for premium model "

tests/model_serving/maas_billing/maas_subscription/test_maas_auth_enforcement.py

Lines changed: 37 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,12 @@
66

77
from tests.model_serving.maas_billing.maas_subscription.utils import (
88
chat_payload_for_url,
9+
create_api_key,
910
poll_expected_status,
11+
revoke_api_key,
1012
)
1113
from tests.model_serving.maas_billing.utils import build_maas_headers
14+
from utilities.general import generate_random_name
1215
from utilities.plugins.constant import RestHeader
1316

1417
LOGGER = structlog.get_logger(name=__name__)
@@ -32,21 +35,41 @@ class TestMaaSAuthPolicyEnforcementTinyLlama:
3235
def test_authorized_user_gets_200(
3336
self,
3437
request_session_http: requests.Session,
38+
base_url: str,
39+
ocp_token_for_actor: str,
3540
model_url_tinyllama_free: str,
36-
maas_headers_for_actor_api_key: dict[str, str],
41+
maas_subscription_tinyllama_free,
3742
) -> None:
38-
payload = chat_payload_for_url(model_url=model_url_tinyllama_free)
39-
40-
resp = poll_expected_status(
43+
"""
44+
Verify a free user with a subscription-bound API key can access the free model.
45+
"""
46+
_, body = create_api_key(
47+
base_url=base_url,
48+
ocp_user_token=ocp_token_for_actor,
4149
request_session_http=request_session_http,
42-
model_url=model_url_tinyllama_free,
43-
headers=maas_headers_for_actor_api_key,
44-
payload=payload,
45-
expected_statuses={200},
50+
api_key_name=f"e2e-auth-enforce-{generate_random_name()}",
51+
subscription=maas_subscription_tinyllama_free.name,
4652
)
53+
api_key = body["key"]
54+
key_id = body["id"]
4755

48-
LOGGER.info(f"test_authorized_user_gets_200 -> POST {model_url_tinyllama_free} returned {resp.status_code}")
49-
assert resp.status_code == 200, f"Expected 200, got {resp.status_code}: {resp.text[:200]}"
56+
try:
57+
resp = poll_expected_status(
58+
request_session_http=request_session_http,
59+
model_url=model_url_tinyllama_free,
60+
headers=build_maas_headers(token=api_key),
61+
payload=chat_payload_for_url(model_url=model_url_tinyllama_free),
62+
expected_statuses={200},
63+
)
64+
LOGGER.info(f"test_authorized_user_gets_200 -> POST {model_url_tinyllama_free} returned {resp.status_code}")
65+
assert resp.status_code == 200, f"Expected 200, got {resp.status_code}: {resp.text[:200]}"
66+
finally:
67+
revoke_api_key(
68+
request_session_http=request_session_http,
69+
base_url=base_url,
70+
key_id=key_id,
71+
ocp_user_token=ocp_token_for_actor,
72+
)
5073

5174
@pytest.mark.smoke
5275
def test_no_auth_header_gets_401(
@@ -99,10 +122,12 @@ def test_wrong_group_sa_denied_on_premium_model(
99122
model_url=model_url_tinyllama_premium,
100123
headers=maas_headers_for_wrong_group_sa,
101124
payload=payload,
102-
expected_statuses={403},
125+
expected_statuses={401, 403},
103126
)
104127
LOGGER.info(
105128
"test_wrong_group_sa_denied_on_premium_model -> "
106129
f"POST {model_url_tinyllama_premium} returned {resp.status_code}"
107130
)
108-
assert resp.status_code == 403, f"Expected 403, got {resp.status_code}: {resp.text[:200]}"
131+
assert resp.status_code in (401, 403), (
132+
f"Expected 401 or 403 (wrong group denied), got {resp.status_code}: {resp.text[:200]}"
133+
)

tests/model_serving/maas_billing/maas_subscription/test_maas_sub_enforcement.py

Lines changed: 38 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,13 @@
44
import requests
55
import structlog
66

7-
from tests.model_serving.maas_billing.maas_subscription.utils import chat_payload_for_url
7+
from tests.model_serving.maas_billing.maas_subscription.utils import (
8+
chat_payload_for_url,
9+
create_api_key,
10+
revoke_api_key,
11+
)
12+
from tests.model_serving.maas_billing.utils import build_maas_headers
13+
from utilities.general import generate_random_name
814

915
LOGGER = structlog.get_logger(name=__name__)
1016

@@ -30,19 +36,41 @@ class TestSubscriptionEnforcementTinyLlama:
3036
def test_subscribed_user_gets_200(
3137
self,
3238
request_session_http: requests.Session,
39+
base_url: str,
40+
ocp_token_for_actor: str,
3341
model_url_tinyllama_premium: str,
34-
maas_headers_for_actor_api_key: dict[str, str],
42+
maas_subscription_tinyllama_premium,
3543
) -> None:
36-
resp = request_session_http.post(
37-
url=model_url_tinyllama_premium,
38-
headers=maas_headers_for_actor_api_key,
39-
json=chat_payload_for_url(model_url=model_url_tinyllama_premium),
40-
timeout=60,
44+
"""
45+
Verify a premium user with a subscription-bound API key can access the premium model.
46+
"""
47+
_, body = create_api_key(
48+
base_url=base_url,
49+
ocp_user_token=ocp_token_for_actor,
50+
request_session_http=request_session_http,
51+
api_key_name=f"e2e-sub-enforce-{generate_random_name()}",
52+
subscription=maas_subscription_tinyllama_premium.name,
4153
)
42-
LOGGER.info(f"test_subscribed_user_gets_200 -> {resp.status_code}")
43-
assert resp.status_code == 200, f"Expected 200, got {resp.status_code}: {resp.text[:200]}"
54+
api_key = body["key"]
55+
key_id = body["id"]
56+
57+
try:
58+
resp = request_session_http.post(
59+
url=model_url_tinyllama_premium,
60+
headers=build_maas_headers(token=api_key),
61+
json=chat_payload_for_url(model_url=model_url_tinyllama_premium),
62+
timeout=60,
63+
)
64+
LOGGER.info(f"test_subscribed_user_gets_200 -> {resp.status_code}")
65+
assert resp.status_code == 200, f"Expected 200, got {resp.status_code}: {resp.text[:200]}"
66+
finally:
67+
revoke_api_key(
68+
request_session_http=request_session_http,
69+
base_url=base_url,
70+
key_id=key_id,
71+
ocp_user_token=ocp_token_for_actor,
72+
)
4473

45-
@pytest.mark.tier1
4674
@pytest.mark.parametrize("ocp_token_for_actor", [{"type": "premium"}], indirect=True)
4775
def test_explicit_subscription_header_works(
4876
self,

tests/model_serving/maas_billing/maas_subscription/utils.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,7 @@ def create_api_key(
187187
request_timeout_seconds: int = 60,
188188
expires_in: str | None = None,
189189
raise_on_error: bool = True,
190+
subscription: str | None = None,
190191
) -> tuple[Response, dict[str, Any]]:
191192
"""
192193
Create an API key via MaaS API and return (response, parsed_body).
@@ -199,12 +200,17 @@ def create_api_key(
199200
When None, no expiresIn field is sent and the key does not expire.
200201
raise_on_error: When True (default), raises AssertionError for non-200/201
201202
responses. Set to False when testing error cases (e.g. 400 rejection).
203+
subscription: Optional MaaSSubscription name to bind at mint time.
204+
When provided, the key is bound to this subscription for inference.
205+
When None, the API auto-selects the highest-priority subscription.
202206
"""
203207
api_keys_url = f"{base_url}/v1/api-keys"
204208

205209
payload: dict[str, Any] = {"name": api_key_name}
206210
if expires_in is not None:
207211
payload["expiresIn"] = expires_in
212+
if subscription is not None:
213+
payload["subscription"] = subscription
208214

209215
response = request_session_http.post(
210216
url=api_keys_url,

0 commit comments

Comments
 (0)