|
| 1 | +from contextlib import ExitStack |
1 | 2 | from typing import Generator |
2 | 3 |
|
3 | 4 | import pytest |
4 | 5 | from _pytest.fixtures import FixtureRequest |
5 | 6 | from kubernetes.dynamic import DynamicClient |
6 | 7 | from ocp_resources.llm_inference_service import LLMInferenceService |
7 | 8 | from ocp_resources.namespace import Namespace |
| 9 | +from ocp_resources.role import Role |
| 10 | +from ocp_resources.role_binding import RoleBinding |
8 | 11 | from ocp_resources.secret import Secret |
9 | 12 | from ocp_resources.service_account import ServiceAccount |
10 | 13 |
|
11 | 14 | from utilities.constants import Timeout, ResourceLimits |
12 | | -from utilities.infra import s3_endpoint_secret |
| 15 | +from utilities.infra import s3_endpoint_secret, create_inference_token |
| 16 | +from utilities.logger import RedactedString |
13 | 17 | from utilities.llmd_utils import create_llmisvc |
14 | 18 | from utilities.llmd_constants import ( |
15 | 19 | ModelStorage, |
@@ -186,3 +190,143 @@ def llmd_inference_service_gpu( |
186 | 190 |
|
187 | 191 | with create_llmisvc(**create_kwargs) as llm_service: |
188 | 192 | yield llm_service |
| 193 | + |
| 194 | + |
| 195 | +@pytest.fixture(scope="class") |
| 196 | +def llmisvc_auth_service_account( |
| 197 | + admin_client: DynamicClient, |
| 198 | + unprivileged_model_namespace: Namespace, |
| 199 | +) -> Generator: |
| 200 | + """Factory fixture to create service accounts for authentication testing.""" |
| 201 | + with ExitStack() as stack: |
| 202 | + |
| 203 | + def _create_service_account(name: str) -> ServiceAccount: |
| 204 | + """Create a single service account.""" |
| 205 | + return stack.enter_context( |
| 206 | + cm=ServiceAccount( |
| 207 | + client=admin_client, |
| 208 | + namespace=unprivileged_model_namespace.name, |
| 209 | + name=name, |
| 210 | + ) |
| 211 | + ) |
| 212 | + |
| 213 | + yield _create_service_account |
| 214 | + |
| 215 | + |
| 216 | +@pytest.fixture(scope="class") |
| 217 | +def llmisvc_auth_view_role( |
| 218 | + admin_client: DynamicClient, |
| 219 | +) -> Generator: |
| 220 | + """Factory fixture to create view roles for LLMInferenceServices.""" |
| 221 | + with ExitStack() as stack: |
| 222 | + |
| 223 | + def _create_view_role(llm_service: LLMInferenceService) -> Role: |
| 224 | + """Create a single view role for a given LLMInferenceService.""" |
| 225 | + return stack.enter_context( |
| 226 | + cm=Role( |
| 227 | + client=admin_client, |
| 228 | + name=f"{llm_service.name}-view", |
| 229 | + namespace=llm_service.namespace, |
| 230 | + rules=[ |
| 231 | + { |
| 232 | + "apiGroups": [llm_service.api_group], |
| 233 | + "resources": ["llminferenceservices"], |
| 234 | + "verbs": ["get"], |
| 235 | + "resourceNames": [llm_service.name], |
| 236 | + }, |
| 237 | + ], |
| 238 | + ) |
| 239 | + ) |
| 240 | + |
| 241 | + yield _create_view_role |
| 242 | + |
| 243 | + |
| 244 | +@pytest.fixture(scope="class") |
| 245 | +def llmisvc_auth_role_binding( |
| 246 | + admin_client: DynamicClient, |
| 247 | +) -> Generator: |
| 248 | + """Factory fixture to create role bindings.""" |
| 249 | + with ExitStack() as stack: |
| 250 | + |
| 251 | + def _create_role_binding( |
| 252 | + service_account: ServiceAccount, |
| 253 | + role: Role, |
| 254 | + ) -> RoleBinding: |
| 255 | + """Create a single role binding.""" |
| 256 | + return stack.enter_context( |
| 257 | + cm=RoleBinding( |
| 258 | + client=admin_client, |
| 259 | + namespace=service_account.namespace, |
| 260 | + name=f"{service_account.name}-view", |
| 261 | + role_ref_name=role.name, |
| 262 | + role_ref_kind=role.kind, |
| 263 | + subjects_kind="ServiceAccount", |
| 264 | + subjects_name=service_account.name, |
| 265 | + ) |
| 266 | + ) |
| 267 | + |
| 268 | + yield _create_role_binding |
| 269 | + |
| 270 | + |
| 271 | +@pytest.fixture(scope="class") |
| 272 | +def llmisvc_auth_token() -> Generator: |
| 273 | + """Factory fixture to create inference tokens with all required RBAC resources.""" |
| 274 | + |
| 275 | + def _create_token( |
| 276 | + service_account: ServiceAccount, |
| 277 | + llmisvc: LLMInferenceService, |
| 278 | + view_role_factory, |
| 279 | + role_binding_factory, |
| 280 | + ) -> str: |
| 281 | + """Create role, role binding, and return an inference token for an existing service account.""" |
| 282 | + # Create role and role binding (these factories manage their own cleanup via ExitStack) |
| 283 | + role = view_role_factory(llm_service=llmisvc) |
| 284 | + role_binding_factory(service_account=service_account, role=role) |
| 285 | + return RedactedString(value=create_inference_token(model_service_account=service_account)) |
| 286 | + |
| 287 | + yield _create_token |
| 288 | + |
| 289 | + |
| 290 | +@pytest.fixture(scope="class") |
| 291 | +def llmisvc_auth( |
| 292 | + admin_client: DynamicClient, |
| 293 | + unprivileged_model_namespace: Namespace, |
| 294 | + llmisvc_auth_service_account, |
| 295 | +) -> Generator: |
| 296 | + """Factory fixture to create LLMInferenceService instances for authentication testing.""" |
| 297 | + with ExitStack() as stack: |
| 298 | + |
| 299 | + def _create_llmd_auth_service( |
| 300 | + service_name: str, |
| 301 | + service_account_name: str, |
| 302 | + storage_uri: str = ModelStorage.TINYLLAMA_OCI, |
| 303 | + container_image: str = ContainerImages.VLLM_CPU, |
| 304 | + container_resources: dict | None = None, |
| 305 | + ) -> tuple[LLMInferenceService, ServiceAccount]: |
| 306 | + """Create a single LLMInferenceService instance with its service account.""" |
| 307 | + if container_resources is None: |
| 308 | + container_resources = { |
| 309 | + "limits": {"cpu": "1", "memory": "10Gi"}, |
| 310 | + "requests": {"cpu": "100m", "memory": "8Gi"}, |
| 311 | + } |
| 312 | + |
| 313 | + # Create the service account first |
| 314 | + sa = llmisvc_auth_service_account(name=service_account_name) |
| 315 | + |
| 316 | + create_kwargs = { |
| 317 | + "client": admin_client, |
| 318 | + "name": service_name, |
| 319 | + "namespace": unprivileged_model_namespace.name, |
| 320 | + "storage_uri": storage_uri, |
| 321 | + "container_image": container_image, |
| 322 | + "container_resources": container_resources, |
| 323 | + "service_account": service_account_name, |
| 324 | + "wait": True, |
| 325 | + "timeout": Timeout.TIMEOUT_15MIN, |
| 326 | + "enable_auth": True, |
| 327 | + } |
| 328 | + |
| 329 | + llm_service = stack.enter_context(cm=create_llmisvc(**create_kwargs)) |
| 330 | + return (llm_service, sa) |
| 331 | + |
| 332 | + yield _create_llmd_auth_service |
0 commit comments