|
6 | 6 | - Creating inference services and related Kubernetes resources |
7 | 7 | - Managing S3 secrets and service accounts |
8 | 8 | - Providing test utilities like snapshots and pod resources |
| 9 | +- OVMS smoke test Pod and ConfigMap for in-cluster script execution |
9 | 10 | """ |
10 | 11 |
|
11 | 12 | import copy |
12 | 13 | from collections.abc import Generator |
| 14 | +from pathlib import Path |
13 | 15 | from typing import Any, cast |
14 | 16 |
|
15 | 17 | import pytest |
16 | 18 | from kubernetes.dynamic import DynamicClient |
17 | 19 | from kubernetes.dynamic.exceptions import ResourceNotFoundError |
| 20 | +from ocp_resources.config_map import ConfigMap |
18 | 21 | from ocp_resources.inference_service import InferenceService |
19 | 22 | from ocp_resources.namespace import Namespace |
20 | 23 | from ocp_resources.pod import Pod |
|
36 | 39 |
|
37 | 40 | LOGGER = get_logger(name=__name__) |
38 | 41 |
|
| 42 | +OVMS_SMOKE_SCRIPTS_DIR = Path(__file__).parent / "smoke" |
| 43 | +OVMS_SMOKE_SCRIPT_NAMES = ("ovms_smoketest.py", "smoke.py") |
| 44 | +OVMS_SMOKE_CONFIGMAP_NAME = "ovms-smoke-scripts" |
| 45 | +OVMS_SMOKE_POD_NAME = "ovms-smoke-pod" |
| 46 | +OVMS_SMOKE_SCRIPTS_MOUNT_PATH = "/scripts" |
| 47 | + |
39 | 48 |
|
40 | 49 | @pytest.fixture(scope="class") |
41 | 50 | def openvino_serving_runtime( |
@@ -189,3 +198,108 @@ def openvino_pod_resource( |
189 | 198 | if not pods: |
190 | 199 | raise ResourceNotFoundError(f"No pods found for InferenceService {openvino_inference_service.name}") |
191 | 200 | return pods[0] |
| 201 | + |
| 202 | + |
| 203 | +def _load_ovms_smoke_scripts_data() -> dict[str, str]: |
| 204 | + """Load smoke script file contents for ConfigMap data.""" |
| 205 | + data: dict[str, str] = {} |
| 206 | + for name in OVMS_SMOKE_SCRIPT_NAMES: |
| 207 | + path = OVMS_SMOKE_SCRIPTS_DIR / name |
| 208 | + data[name] = path.read_text() |
| 209 | + return data |
| 210 | + |
| 211 | + |
| 212 | +@pytest.fixture(scope="class") |
| 213 | +def ovms_smoke_scripts_configmap( |
| 214 | + admin_client: DynamicClient, |
| 215 | + model_namespace: Namespace, |
| 216 | +) -> Generator[ConfigMap]: |
| 217 | + """ |
| 218 | + ConfigMap containing OVMS smoke test scripts to run inside the container. |
| 219 | +
|
| 220 | + Args: |
| 221 | + admin_client: Kubernetes dynamic client. |
| 222 | + model_namespace: Namespace for the ConfigMap. |
| 223 | +
|
| 224 | + Yields: |
| 225 | + ConfigMap: ConfigMap with ovms_smoketest.py and smoke.py data. |
| 226 | + """ |
| 227 | + data = _load_ovms_smoke_scripts_data() |
| 228 | + with ConfigMap( |
| 229 | + client=admin_client, |
| 230 | + name=OVMS_SMOKE_CONFIGMAP_NAME, |
| 231 | + namespace=model_namespace.name, |
| 232 | + data=data, |
| 233 | + ) as cm: |
| 234 | + yield cm |
| 235 | + |
| 236 | + |
| 237 | +@pytest.fixture(scope="class") |
| 238 | +def ovms_smoke_pod( |
| 239 | + admin_client: DynamicClient, |
| 240 | + model_namespace: Namespace, |
| 241 | + ovms_runtime_image: str, |
| 242 | + ovms_smoke_scripts_configmap: ConfigMap, |
| 243 | +) -> Generator[Pod]: |
| 244 | + """ |
| 245 | + Pod that runs OVMS smoke scripts inside OpenShift using the OVMS runtime image. |
| 246 | +
|
| 247 | + The smoke scripts are mounted read-only via ConfigMap (not copied). |
| 248 | + The container runs both scripts in sequence; the Pod succeeds only if both exit 0. |
| 249 | +
|
| 250 | + Args: |
| 251 | + admin_client: Kubernetes dynamic client. |
| 252 | + model_namespace: Namespace for the Pod. |
| 253 | + ovms_runtime_image: Container image for OVMS runtime (from CLI or template). |
| 254 | + ovms_smoke_scripts_configmap: ConfigMap with smoke script contents. |
| 255 | +
|
| 256 | + Yields: |
| 257 | + Pod: The completed Pod resource (phase Succeeded when both scripts exit 0). |
| 258 | + """ |
| 259 | + run_cmd = ( |
| 260 | + f"python {OVMS_SMOKE_SCRIPTS_MOUNT_PATH}/ovms_smoketest.py && python {OVMS_SMOKE_SCRIPTS_MOUNT_PATH}/smoke.py" |
| 261 | + ) |
| 262 | + # Use writable dirs under /tmp so non-root container can cache models and configs. |
| 263 | + # HF_HOME is the preferred cache for Hugging Face (TRANSFORMERS_CACHE is deprecated in v5). |
| 264 | + env_vars = [ |
| 265 | + {"name": "HOME", "value": "/tmp"}, |
| 266 | + {"name": "HF_HOME", "value": "/tmp/hf_cache"}, |
| 267 | + {"name": "MPLCONFIGDIR", "value": "/tmp/matplotlib"}, |
| 268 | + ] |
| 269 | + with Pod( |
| 270 | + client=admin_client, |
| 271 | + name=OVMS_SMOKE_POD_NAME, |
| 272 | + namespace=model_namespace.name, |
| 273 | + restart_policy="Never", |
| 274 | + containers=[ |
| 275 | + { |
| 276 | + "name": "ovms-smoke", |
| 277 | + "image": ovms_runtime_image, |
| 278 | + "command": ["/bin/sh", "-c"], |
| 279 | + "args": [run_cmd], |
| 280 | + "env": env_vars, |
| 281 | + "volumeMounts": [ |
| 282 | + { |
| 283 | + "name": "smoke-scripts", |
| 284 | + "mountPath": OVMS_SMOKE_SCRIPTS_MOUNT_PATH, |
| 285 | + "readOnly": True, |
| 286 | + } |
| 287 | + ], |
| 288 | + "securityContext": { |
| 289 | + "allowPrivilegeEscalation": False, |
| 290 | + "capabilities": {"drop": ["ALL"]}, |
| 291 | + "runAsNonRoot": True, |
| 292 | + "seccompProfile": {"type": "RuntimeDefault"}, |
| 293 | + }, |
| 294 | + } |
| 295 | + ], |
| 296 | + volumes=[ |
| 297 | + { |
| 298 | + "name": "smoke-scripts", |
| 299 | + "configMap": {"name": ovms_smoke_scripts_configmap.name}, |
| 300 | + } |
| 301 | + ], |
| 302 | + ) as pod: |
| 303 | + LOGGER.info("Waiting for OVMS smoke Pod to complete") |
| 304 | + pod.wait_for_status(status=Pod.Status.SUCCEEDED, timeout=300) |
| 305 | + yield pod |
0 commit comments