Skip to content

Commit bdf574b

Browse files
committed
maas-billing: refactor RBAC fixtures
1 parent 32bc7cf commit bdf574b

File tree

3 files changed

+183
-146
lines changed

3 files changed

+183
-146
lines changed

tests/model_serving/model_server/maas_billing/conftest.py

Lines changed: 143 additions & 111 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
import pytest
55
import requests
66
from simple_logger.logger import get_logger
7-
from utilities.plugins.constant import RestHeader, OpenAIEnpoints
7+
from utilities.plugins.constant import OpenAIEnpoints
88

99
from kubernetes.dynamic import DynamicClient
1010
from ocp_resources.infrastructure import Infrastructure
@@ -15,15 +15,14 @@
1515
from utilities.infra import login_with_user_password, get_openshift_token
1616
from utilities.general import wait_for_oauth_openshift_deployment
1717
from ocp_resources.secret import Secret
18-
from timeout_sampler import TimeoutExpiredError
19-
20-
2118
from tests.model_serving.model_server.maas_billing.utils import (
2219
detect_scheme_via_llmisvc,
2320
host_from_ingress_domain,
2421
mint_token,
2522
llmis_name,
2623
create_maas_group,
24+
build_maas_headers,
25+
get_maas_models_response,
2726
)
2827

2928

@@ -53,7 +52,7 @@ def minted_token(request_session_http, base_url: str, current_client_token: str)
5352
minutes=30,
5453
http_session=request_session_http,
5554
)
56-
LOGGER.info("Mint token response status=%s", resp.status_code)
55+
LOGGER.info(f"Mint token response status={resp.status_code}")
5756
assert resp.status_code in (200, 201), f"mint failed: {resp.status_code} {resp.text[:200]}"
5857
token = body.get("token", "")
5958
assert isinstance(token, str) and len(token) > 10, f"no usable token in response: {body}"
@@ -81,22 +80,20 @@ def model_url(admin_client) -> str:
8180

8281
@pytest.fixture
8382
def maas_headers(minted_token: str) -> dict:
84-
"""Common headers for MaaS API calls."""
85-
return {"Authorization": f"Bearer {minted_token}", **RestHeader.HEADERS}
83+
return build_maas_headers(token=minted_token)
8684

8785

8886
@pytest.fixture
8987
def maas_models(
90-
request_session_http: requests.Session,
91-
base_url: str,
92-
maas_headers: dict,
88+
request_session_http,
89+
base_url,
90+
maas_headers,
9391
):
94-
"""
95-
Call /v1/models once and return the list of models.
96-
97-
"""
98-
models_url = f"{base_url}{MODELS_INFO}"
99-
resp = request_session_http.get(models_url, headers=maas_headers, timeout=60)
92+
resp = get_maas_models_response(
93+
session=request_session_http,
94+
base_url=base_url,
95+
headers=maas_headers,
96+
)
10097

10198
assert resp.status_code == 200, f"/v1/models failed: {resp.status_code} {resp.text[:200]}"
10299

@@ -133,113 +130,129 @@ def maas_user_credentials_both() -> dict[str, str]:
133130

134131

135132
@pytest.fixture(scope="session")
136-
def maas_rbac_idp_env(
137-
admin_client: DynamicClient,
133+
def maas_htpasswd_files(
138134
maas_user_credentials_both: dict[str, str],
139-
is_byoidc: bool,
140-
) -> Generator[dict[str, str], None, None]:
141-
"""
142-
- Creates a single htpasswd Secret with FREE + PREMIUM users.
143-
- Adds a temporary MaaS HTPasswd IDP to oauth/cluster using ResourceEditor.
144-
- Waits for oauth-openshift rollout after patch.
145-
- On teardown, ResourceEditor restores the original OAuth spec and we wait again.
146-
- Deletes the temporary htpasswd Secret.
135+
) -> Generator[tuple[str, str, str, str], None, None]:
147136
"""
148-
if is_byoidc:
149-
pytest.skip("Working on OIDC support for tests that use htpasswd IDP for MaaS")
137+
Create per-user htpasswd files for FREE and PREMIUM users and return
138+
their file paths + base64 contents.
150139
140+
Cleanup of the temp files happens at teardown.
141+
"""
151142
free_username = maas_user_credentials_both["free_user"]
152143
free_password = maas_user_credentials_both["free_pass"]
153144
premium_username = maas_user_credentials_both["premium_user"]
154145
premium_password = maas_user_credentials_both["premium_pass"]
155-
secret_name = maas_user_credentials_both["secret_name"]
156-
idp_name = maas_user_credentials_both["idp_name"]
157146

158147
free_htpasswd_file_path, free_htpasswd_b64 = create_htpasswd_file(
159148
username=free_username,
160149
password=free_password,
161150
)
162-
163151
premium_htpasswd_file_path, premium_htpasswd_b64 = create_htpasswd_file(
164152
username=premium_username,
165153
password=premium_password,
166154
)
167155

168156
try:
169-
free_bytes = base64.b64decode(s=free_htpasswd_b64)
170-
premium_bytes = base64.b64decode(s=premium_htpasswd_b64)
171-
combined_bytes = free_bytes + b"\n" + premium_bytes
172-
combined_htpasswd_b64 = base64.b64encode(s=combined_bytes).decode("utf-8")
173-
174-
oauth_resource = OAuth(name="cluster", client=admin_client)
175-
oauth_spec = getattr(oauth_resource.instance, "spec", {}) or {}
176-
existing_identity_providers = oauth_spec.get("identityProviders") or []
177-
178-
maas_identity_provider = {
179-
"name": idp_name,
180-
"mappingMethod": "claim",
181-
"type": "HTPasswd",
182-
"challenge": True,
183-
"login": True,
184-
"htpasswd": {"fileData": {"name": secret_name}},
185-
}
186-
187-
updated_identity_providers = existing_identity_providers + [maas_identity_provider]
188-
189-
LOGGER.info(
190-
f"MaaS RBAC: creating shared htpasswd Secret '{secret_name}' for users "
191-
f"'{free_username}' and '{premium_username}'"
157+
yield (
158+
free_htpasswd_file_path,
159+
free_htpasswd_b64,
160+
premium_htpasswd_file_path,
161+
premium_htpasswd_b64,
192162
)
163+
finally:
164+
free_htpasswd_file_path.unlink(missing_ok=True)
165+
premium_htpasswd_file_path.unlink(missing_ok=True)
193166

194-
# --- update OAuth + create Secret ---
195-
with (
196-
Secret(
197-
client=admin_client,
198-
name=secret_name,
199-
namespace="openshift-config",
200-
htpasswd=combined_htpasswd_b64,
201-
type="Opaque",
202-
teardown=True,
203-
wait_for_resource=True,
204-
),
205-
ResourceEditor(patches={oauth_resource: {"spec": {"identityProviders": updated_identity_providers}}}),
206-
):
207-
LOGGER.info(f"MaaS RBAC: updating OAuth with MaaS htpasswd IDP '{maas_identity_provider['name']}'")
208-
209-
wait_for_oauth_openshift_deployment()
210-
LOGGER.info(f"MaaS RBAC: OAuth updated with MaaS IDP '{maas_identity_provider['name']}'")
211-
212-
# >>> this is the yield that pytest uses <<<
213-
yield maas_user_credentials_both
214-
215-
LOGGER.info("MaaS RBAC: restoring OAuth identityProviders to original state")
216-
217-
# --- after exit: secret deleted + OAuth restored ---
218-
try:
219-
wait_for_oauth_openshift_deployment()
220-
LOGGER.info("MaaS RBAC: oauth-openshift rollout completed after restoring OAuth.")
221-
except TimeoutExpiredError as timeout_error:
222-
LOGGER.warning(
223-
f"MaaS RBAC: timeout while waiting for oauth-openshift rollout after restoring OAuth. "
224-
f"Continuing teardown. Details: {timeout_error}"
225-
)
226167

227-
# --- final sanity check ---
228-
oauth_resource_after = OAuth(name="cluster", client=admin_client)
229-
oauth_spec_after = getattr(oauth_resource_after.instance, "spec", {}) or {}
230-
current_idps = oauth_spec_after.get("identityProviders") or []
168+
@pytest.fixture(scope="session")
169+
def maas_htpasswd_oauth_idp(
170+
admin_client: DynamicClient,
171+
maas_user_credentials_both: dict[str, str],
172+
maas_htpasswd_files: tuple[str, str, str, str],
173+
is_byoidc: bool,
174+
):
175+
"""
176+
- Combines FREE + PREMIUM htpasswd entries into a single Secret.
177+
- Adds the MaaS HTPasswd IDP to oauth/cluster using ResourceEditor.
178+
- Waits for oauth-openshift rollout after patch.
179+
- On teardown, waits again and verifies the IDP is gone.
180+
"""
181+
if is_byoidc:
182+
pytest.skip("Working on OIDC support for tests that use htpasswd IDP for MaaS")
183+
184+
(
185+
_free_htpasswd_file_path,
186+
free_htpasswd_b64,
187+
_premium_htpasswd_file_path,
188+
premium_htpasswd_b64,
189+
) = maas_htpasswd_files
231190

232-
if any(idp.get("name") == idp_name for idp in current_idps):
233-
LOGGER.warning(
234-
f"MaaS RBAC: temporary MaaS IDP '{idp_name}' is STILL present in OAuth spec "
235-
f"after teardown — please investigate cluster OAuth configuration."
236-
)
237-
else:
238-
LOGGER.info("MaaS RBAC: OAuth identityProviders restoration & cleanup completed")
191+
free_username = maas_user_credentials_both["free_user"]
192+
premium_username = maas_user_credentials_both["premium_user"]
193+
secret_name = maas_user_credentials_both["secret_name"]
194+
idp_name = maas_user_credentials_both["idp_name"]
239195

240-
finally:
241-
free_htpasswd_file_path.unlink(missing_ok=True)
242-
premium_htpasswd_file_path.unlink(missing_ok=True)
196+
free_bytes = base64.b64decode(s=free_htpasswd_b64)
197+
premium_bytes = base64.b64decode(s=premium_htpasswd_b64)
198+
combined_bytes = free_bytes + b"\n" + premium_bytes
199+
combined_htpasswd_b64 = base64.b64encode(s=combined_bytes).decode("utf-8")
200+
201+
oauth_resource = OAuth(name="cluster", client=admin_client)
202+
oauth_spec = getattr(oauth_resource.instance, "spec", {}) or {}
203+
existing_idps = oauth_spec.get("identityProviders") or []
204+
205+
maas_idp = {
206+
"name": idp_name,
207+
"mappingMethod": "claim",
208+
"type": "HTPasswd",
209+
"challenge": True,
210+
"login": True,
211+
"htpasswd": {"fileData": {"name": secret_name}},
212+
}
213+
214+
updated_idps = existing_idps + [maas_idp]
215+
216+
LOGGER.info(
217+
f"MaaS RBAC: creating shared htpasswd Secret '{secret_name}' "
218+
f"for users '{free_username}' and '{premium_username}'"
219+
)
220+
221+
with (
222+
Secret(
223+
client=admin_client,
224+
name=secret_name,
225+
namespace="openshift-config",
226+
htpasswd=combined_htpasswd_b64,
227+
type="Opaque",
228+
teardown=True,
229+
wait_for_resource=True,
230+
),
231+
ResourceEditor(patches={oauth_resource: {"spec": {"identityProviders": updated_idps}}}),
232+
):
233+
LOGGER.info(f"MaaS RBAC: updating OAuth with MaaS htpasswd IDP '{maas_idp['name']}'")
234+
wait_for_oauth_openshift_deployment()
235+
LOGGER.info(f"MaaS RBAC: OAuth updated with MaaS IDP '{maas_idp['name']}'")
236+
yield
237+
238+
# teardown checks
239+
wait_for_oauth_openshift_deployment()
240+
241+
oauth_after = OAuth(name="cluster", client=admin_client)
242+
idps_after = getattr(oauth_after.instance, "spec", {}).get("identityProviders") or []
243+
if any(idp.get("name") == idp_name for idp in idps_after):
244+
pytest.fail(f"MaaS RBAC: cleanup failed, IDP {idp_name} still present after teardown")
245+
246+
LOGGER.info("MaaS RBAC: OAuth identityProviders restoration & cleanup completed")
247+
248+
249+
@pytest.fixture(scope="session")
250+
def maas_rbac_idp_env(
251+
maas_htpasswd_oauth_idp,
252+
maas_user_credentials_both: dict[str, str],
253+
):
254+
255+
return maas_user_credentials_both
243256

244257

245258
@pytest.fixture(scope="session")
@@ -289,7 +302,7 @@ def maas_premium_user_session(
289302
original_user: str,
290303
maas_api_server_url: str,
291304
is_byoidc: bool,
292-
maas_rbac_idp_env: dict[str, str], # <-- same outer fixture
305+
maas_rbac_idp_env: dict[str, str],
293306
) -> Generator[UserTestSession, None, None]:
294307
if is_byoidc:
295308
pytest.skip("Working on OIDC support for tests that use htpasswd IDP for MaaS")
@@ -368,27 +381,26 @@ def ocp_token_for_actor(
368381
"""
369382
Log in as the requested actor ('admin' / 'free' / 'premium')
370383
and yield the OpenShift token for that user.
371-
372384
"""
373385
actor_param = getattr(request, "param", None)
374386

375387
if isinstance(actor_param, dict):
376-
actor_kind = actor_param.get("kind", "admin")
388+
actor_type = actor_param.get("type", "admin")
377389
else:
378-
actor_kind = actor_param or "admin"
390+
actor_type = actor_param or "admin"
379391

380-
if actor_kind == "admin":
392+
if actor_type == "admin":
381393
LOGGER.info("MaaS RBAC: using existing admin session to obtain token")
382394
yield get_openshift_token(client=admin_client)
383395
else:
384-
if actor_kind == "free":
396+
if actor_type == "free":
385397
user_session = maas_free_user_session
386-
elif actor_kind == "premium":
398+
elif actor_type == "premium":
387399
user_session = maas_premium_user_session
388400
else:
389-
raise ValueError(f"Unknown actor kind: {actor_kind!r}")
401+
raise ValueError(f"Unknown actor type: {actor_type!r}")
390402

391-
LOGGER.info(f"MaaS RBAC: logging in as MaaS {actor_kind} user '{user_session.username}'")
403+
LOGGER.info(f"MaaS RBAC: logging in as MaaS {actor_type} user '{user_session.username}'")
392404
login_successful = login_with_user_password(
393405
api_address=maas_api_server_url,
394406
user=user_session.username,
@@ -426,11 +438,31 @@ def maas_token_for_actor(
426438
http_session=request_session_http,
427439
minutes=30,
428440
)
429-
LOGGER.info("MaaS RBAC: mint token status=%s", response.status_code)
441+
LOGGER.info(f"MaaS RBAC: mint token status={response.status_code}")
430442
assert response.status_code in (200, 201), f"mint failed: {response.status_code} {response.text[:200]}"
431443

432444
token = body.get("token", "")
433445
assert isinstance(token, str) and len(token) > 10, "no usable MaaS token in response"
434446

435-
LOGGER.info("MaaS RBAC: minted MaaS token len=%s for current actor", len(token))
447+
LOGGER.info(f"MaaS RBAC: minted MaaS token len={len(token)} for current actor")
436448
return token
449+
450+
451+
@pytest.fixture
452+
def maas_headers_for_actor(maas_token_for_actor: str) -> dict:
453+
"""Headers for the current actor (admin/free/premium)."""
454+
return build_maas_headers(token=maas_token_for_actor)
455+
456+
457+
@pytest.fixture
458+
def maas_models_response_for_actor(
459+
request_session_http: requests.Session,
460+
base_url: str,
461+
maas_headers_for_actor: dict,
462+
):
463+
"""Raw /v1/models response for the current actor."""
464+
return get_maas_models_response(
465+
session=request_session_http,
466+
base_url=base_url,
467+
headers=maas_headers_for_actor,
468+
)

0 commit comments

Comments
 (0)