Skip to content

Commit a845a70

Browse files
JoohoSnomaan6846Raghul-M
authored
feat: add model car (OCI image) deployment tests (#1162)
* test(mlserver): add model car (OCI image) deployment tests - Add MLServer model car tests for sklearn, xgboost, and lightgbm using OCI images. - Add mlserver_model_car_inference_service fixture with env variable support. - Add Standard deployment mode support in constants and utils Signed-off-by: Jooho Lee <jlee@redhat.com> * chore: align mlserver s3 and model-car test-cases Signed-off-by: Snomaan6846 <syedali@redhat.com> rh-pre-commit.version: 2.3.2 rh-pre-commit.check-secrets: ENABLED --------- Signed-off-by: Jooho Lee <jlee@redhat.com> Co-authored-by: Snomaan6846 <syedali@redhat.com> Co-authored-by: RAGHUL M <ragm@redhat.com>
1 parent 9031571 commit a845a70

20 files changed

Lines changed: 624 additions & 64 deletions
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
# MLServer Runtime Test Suite
2+
3+
End-to-end tests for MLServer model serving on OpenShift AI / OpenDataHub.
4+
This suite validates inference behavior across different model source strategies and formats.
5+
6+
## Sub-suites
7+
8+
- S3 model source: [`s3/README.md`](./s3/README.md)
9+
- OCI model car source: [`model_car/README.md`](./model_car/README.md)
10+
11+
## What This Suite Covers
12+
13+
- Protocol: REST inference
14+
- Deployment mode: `RawDeployment`
15+
- Model formats: sklearn, xgboost, lightgbm, onnx
16+
- Response validation: snapshot-based assertions
17+
18+
## Run All MLServer Tests
19+
20+
```bash
21+
OC_BINARY_PATH=/usr/local/bin/oc uv run pytest tests/model_serving/model_runtime/mlserver \
22+
--aws-access-key-id=<aws-access-key-id> \
23+
--aws-secret-access-key=<aws-secret-access-key> \
24+
--models-s3-bucket-name=<bucket-name> \
25+
--models-s3-bucket-region=<region> \
26+
--models-s3-bucket-endpoint=<endpoint-url>
27+
```
28+
29+
Optional runtime image override (use only for custom/private image validation):
30+
31+
```bash
32+
--mlserver-runtime-image=<mlserver-image>
33+
```
34+
35+
## Snapshot Updates
36+
37+
```bash
38+
OC_BINARY_PATH=/usr/local/bin/oc uv run pytest tests/model_serving/model_runtime/mlserver \
39+
--snapshot-update \
40+
--aws-access-key-id=<aws-access-key-id> \
41+
--aws-secret-access-key=<aws-secret-access-key> \
42+
--models-s3-bucket-name=<bucket-name> \
43+
--models-s3-bucket-region=<region> \
44+
--models-s3-bucket-endpoint=<endpoint-url>
45+
```

tests/model_serving/model_runtime/mlserver/conftest.py

Lines changed: 46 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ def mlserver_serving_runtime(
6060
name=ModelInferenceRuntime.MLSERVER_RUNTIME,
6161
namespace=model_namespace.name,
6262
template_name=RuntimeTemplates.MLSERVER,
63-
deployment_type=request.param["deployment_type"],
63+
deployment_type=request.param["deployment_mode"],
6464
runtime_image=mlserver_runtime_image,
6565
) as model_runtime:
6666
yield model_runtime
@@ -98,7 +98,7 @@ def mlserver_inference_service(
9898
"storage_uri": s3_models_storage_uri,
9999
"model_format": mlserver_serving_runtime.instance.spec.supportedModelFormats[0].name,
100100
"model_service_account": mlserver_model_service_account.name,
101-
"deployment_mode": params.get("deployment_type", KServeDeploymentType.RAW_DEPLOYMENT),
101+
"deployment_mode": params.get("deployment_mode", KServeDeploymentType.RAW_DEPLOYMENT),
102102
"external_route": params.get("enable_external_route", False),
103103
}
104104

@@ -146,6 +146,50 @@ def mlserver_model_service_account(admin_client: DynamicClient, kserve_s3_secret
146146
yield sa
147147

148148

149+
@pytest.fixture(scope="class")
150+
def mlserver_model_car_inference_service(
151+
request: pytest.FixtureRequest,
152+
admin_client: DynamicClient,
153+
model_namespace: Namespace,
154+
mlserver_serving_runtime: ServingRuntime,
155+
) -> Generator[InferenceService]:
156+
"""
157+
Create InferenceService for MLServer model car (OCI image) testing.
158+
159+
Args:
160+
request: Pytest fixture request with parameters.
161+
admin_client: Kubernetes dynamic client.
162+
model_namespace: Namespace for deployment.
163+
mlserver_serving_runtime: MLServer ServingRuntime instance.
164+
165+
Yields:
166+
InferenceService: Configured ISVC using OCI storage.
167+
"""
168+
params = request.param
169+
storage_uri = params.get("storage-uri")
170+
if not storage_uri:
171+
raise ValueError("storage-uri is required in params")
172+
173+
deployment_mode = params.get("deployment_mode", KServeDeploymentType.RAW_DEPLOYMENT)
174+
model_format = params.get("model-format")
175+
if not model_format:
176+
raise ValueError("model-format is required in params")
177+
178+
with create_isvc(
179+
client=admin_client,
180+
name=f"{model_format}-modelcar",
181+
namespace=model_namespace.name,
182+
runtime=mlserver_serving_runtime.name,
183+
storage_uri=storage_uri,
184+
model_format=model_format,
185+
deployment_mode=deployment_mode,
186+
external_route=params.get("enable_external_route"),
187+
wait_for_predictor_pods=params.get("wait_for_predictor_pods", False),
188+
model_env_variables=params.get("model_env_variables"),
189+
) as isvc:
190+
yield isvc
191+
192+
149193
@pytest.fixture
150194
def mlserver_response_snapshot(snapshot: Any) -> Any:
151195
"""

tests/model_serving/model_runtime/mlserver/constant.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ class OutputType:
1818

1919

2020
LOCALHOST_URL: str = "http://localhost"
21-
RAW_DEPLOYMENT_TYPE: str = "raw"
2221
MODEL_PATH_PREFIX: str = "mlserver/model_repository"
2322

2423
PREDICT_RESOURCES: dict[str, list[dict[str, str | dict[str, str]]] | dict[str, dict[str, str]]] = {
@@ -36,7 +35,7 @@ class OutputType:
3635
}
3736

3837
BASE_RAW_DEPLOYMENT_CONFIG: dict[str, Any] = {
39-
"deployment_type": KServeDeploymentType.RAW_DEPLOYMENT,
38+
"deployment_mode": KServeDeploymentType.RAW_DEPLOYMENT,
4039
"min-replicas": 1,
4140
"enable_external_route": False,
4241
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
# MLServer Model Car (OCI Image) Tests
2+
3+
End-to-end tests for MLServer inference using model car (OCI image-based) deployments.
4+
Models are packaged as OCI container images and deployed via KServe InferenceService with `storageUri: oci://...`.
5+
6+
Main suite overview:
7+
[`mlserver/README.md`](../README.md)
8+
9+
## Supported Model Formats
10+
11+
- sklearn
12+
- xgboost
13+
- lightgbm
14+
- onnx
15+
16+
## OCI Model Images
17+
18+
The OCI model images used in these tests are built from:
19+
<https://github.com/Jooho/oci-model-images>
20+
21+
If the version of a supported framework (xgboost, lightgbm, sklearn, onnx) changes in MLServer,
22+
the model images must be rebuilt and pushed from that repository.
23+
24+
The framework versions used by MLServer can be found at:
25+
<https://github.com/red-hat-data-services/MLServer/blob/main/requirements/requirements-cpu.txt#L261>
26+
27+
For e2e testing, images should be tagged with the `-e2e` suffix to pin stable versions.
28+
29+
## Running Tests
30+
31+
Run all model car tests:
32+
33+
```bash
34+
OC_BINARY_PATH=/usr/local/bin/oc uv run pytest tests/model_serving/model_runtime/mlserver/model_car
35+
```
36+
37+
Run a specific model format (e.g., onnx only):
38+
39+
```bash
40+
OC_BINARY_PATH=/usr/local/bin/oc uv run pytest tests/model_serving/model_runtime/mlserver/model_car -k "onnx"
41+
```
42+
43+
## Updating Snapshots
44+
45+
If model responses change and snapshots need to be updated, add the `--snapshot-update` flag:
46+
47+
```bash
48+
OC_BINARY_PATH=/usr/local/bin/oc uv run pytest tests/model_serving/model_runtime/mlserver/model_car --snapshot-update
49+
```
50+
51+
## Related Suite
52+
53+
For S3-based MLServer tests, see:
54+
[`mlserver/s3/README.md`](../s3/README.md)

tests/model_serving/model_runtime/mlserver/basic_model_deployment/__init__.py renamed to tests/model_serving/model_runtime/mlserver/model_car/__init__.py

File renamed without changes.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
{
2+
"id": "lightgbm",
3+
"model_name": "lightgbm-modelcar",
4+
"outputs": [
5+
{
6+
"data": [
7+
0.0017564886970520908,
8+
0.008535872687680112,
9+
0.9897076386152678
10+
],
11+
"datatype": "FP64",
12+
"name": "predict",
13+
"parameters": {
14+
"content_type": "np"
15+
},
16+
"shape": [
17+
1,
18+
3
19+
]
20+
}
21+
],
22+
"parameters": {}
23+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
{
2+
"id": "lightgbm",
3+
"model_name": "lightgbm-modelcar",
4+
"outputs": [
5+
{
6+
"data": [
7+
0.0017564886970520908,
8+
0.008535872687680112,
9+
0.9897076386152678
10+
],
11+
"datatype": "FP64",
12+
"name": "predict",
13+
"parameters": {
14+
"content_type": "np"
15+
},
16+
"shape": [
17+
1,
18+
3
19+
]
20+
}
21+
],
22+
"parameters": {}
23+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
{
2+
"id": "onnx",
3+
"model_name": "onnx-modelcar",
4+
"outputs": [
5+
{
6+
"data": [
7+
4.1880645751953125,
8+
2.2079954147338867,
9+
-4.680917263031006
10+
],
11+
"datatype": "FP32",
12+
"name": "predict",
13+
"parameters": {
14+
"content_type": "np"
15+
},
16+
"shape": [
17+
1,
18+
3
19+
]
20+
}
21+
],
22+
"parameters": {}
23+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
{
2+
"id": "sklearn",
3+
"model_name": "sklearn-modelcar",
4+
"outputs": [
5+
{
6+
"data": [
7+
1,
8+
1
9+
],
10+
"datatype": "INT64",
11+
"name": "predict",
12+
"parameters": {
13+
"content_type": "np"
14+
},
15+
"shape": [
16+
2,
17+
1
18+
]
19+
}
20+
],
21+
"parameters": {}
22+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
{
2+
"id": "xgboost",
3+
"model_name": "xgboost-modelcar",
4+
"outputs": [
5+
{
6+
"data": [
7+
1.0,
8+
1.0
9+
],
10+
"datatype": "FP32",
11+
"name": "predict",
12+
"parameters": {
13+
"content_type": "np"
14+
},
15+
"shape": [
16+
2,
17+
1
18+
]
19+
}
20+
],
21+
"parameters": {}
22+
}

0 commit comments

Comments
 (0)