Skip to content

feat: add model car (OCI image) deployment tests#1162

Open
Jooho wants to merge 6 commits intoopendatahub-io:mainfrom
Jooho:mlserver/modelcar_test
Open

feat: add model car (OCI image) deployment tests#1162
Jooho wants to merge 6 commits intoopendatahub-io:mainfrom
Jooho:mlserver/modelcar_test

Conversation

@Jooho
Copy link
Copy Markdown
Contributor

@Jooho Jooho commented Mar 3, 2026

Pull Request

Summary

  • Add MLServer model car tests for sklearn, xgboost, lightgbm, and onnx using OCI images
  • Add mlserver_model_car_inference_service fixture with env variable support
  • Add Standard deployment mode support in constants and utils
  • Add ONNX model car test case and response snapshot
  • Add README documenting OCI image source, test execution, and snapshot update instructions
  • OCI images built from Jooho/oci-model-images

Related Issues

How it has been tested

  • Locally
  • Jenkins

Additional Requirements

  • If this PR introduces a new test image, did you create a PR to mirror it in disconnected environment?
  • If this PR introduces new marker(s)/adds a new component, was relevant ticket created to update relevant Jenkins job?

Summary by CodeRabbit

  • New Features

    • Added MLServer "model car" (OCI image) deployment support for scikit-learn, XGBoost, LightGBM, and ONNX; introduced STANDARD and Knative deployment mode types.
  • Tests

    • Added end-to-end inference tests and expected-output snapshots across multiple model formats and deployment variants.
  • Documentation

    • Added guide for MLServer model car e2e tests, supported formats, and running/updating snapshots.

@github-actions
Copy link
Copy Markdown

github-actions bot commented Mar 3, 2026

The following are automatically added/executed:

  • PR size label.
  • Run pre-commit
  • Run tox
  • Add PR author as the PR assignee
  • Build image based on the PR

Available user actions:

  • To mark a PR as WIP, add /wip in a comment. To remove it from the PR comment /wip cancel to the PR.
  • To block merging of a PR, add /hold in a comment. To un-block merging of PR comment /hold cancel.
  • To mark a PR as approved, add /lgtm in a comment. To remove, add /lgtm cancel.
    lgtm label removed on each new commit push.
  • To mark PR as verified comment /verified to the PR, to un-verify comment /verified cancel to the PR.
    verified label removed on each new commit push.
  • To Cherry-pick a merged PR /cherry-pick <target_branch_name> to the PR. If <target_branch_name> is valid,
    and the current PR is merged, a cherry-picked PR would be created and linked to the current PR.
  • To build and push image to quay, add /build-push-pr-image in a comment. This would create an image with tag
    pr-<pr_number> to quay repository. This image tag, however would be deleted on PR merge or close action.
Supported labels

{'/lgtm', '/verified', '/cherry-pick', '/wip', '/hold', '/build-push-pr-image'}

@Jooho
Copy link
Copy Markdown
Contributor Author

Jooho commented Mar 3, 2026

/hold wait for opendatahub-io/odh-model-controller#703

@Jooho Jooho marked this pull request as ready for review March 17, 2026 21:37
@Jooho Jooho requested review from a team, Raghul-M and brettmthompson as code owners March 17, 2026 21:37
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Mar 17, 2026

📝 Walkthrough

Walkthrough

Adds MLServer “model car” (OCI image) e2e test support: new pytest fixture and parameterized tests, multiple MLServer model snapshots, utilities and constants to generate OCI storage/namespace/configs, and deployment-mode handling adjustments. Includes new README and snapshot artifacts.

Changes

Cohort / File(s) Summary
Test fixtures & tests
tests/model_serving/model_runtime/mlserver/conftest.py, tests/model_serving/model_runtime/mlserver/model_car/test_mlserver_model_car.py
New class-scoped pytest fixture mlserver_model_car_inference_service that creates an InferenceService for OCI-backed MLServer images (requires model-format); new parameterized TestMLServerModelCar.test_mlserver_model_car_inference exercising REST inference across formats and a LightGBM text-type variant.
Snapshots & docs
tests/model_serving/model_runtime/mlserver/model_car/__snapshots__/*, tests/model_serving/model_runtime/mlserver/model_car/README.md
Added JSON snapshots for sklearn, xgboost, lightgbm (two variants), and onnx model outputs; added README documenting MLServer model car e2e tests and recommended image tagging.
Test utilities
tests/model_serving/model_runtime/mlserver/utils.py
Expanded helpers to support modelcar (OCI) deployments: get_model_storage_uri_dict, get_model_namespace_dict, get_deployment_config_dict, and get_test_case_id now accept modelcar flags and optional env variables; run_mlserver_inference now allows STANDARD in addition to RAW_DEPLOYMENT.
Constants
utilities/constants.py
Added KServeDeploymentType.STANDARD and KNATIVE; introduced MLServer OCI image URIs in ModelCarImage for sklearn/xgboost/lightgbm/onnx (placeholders). Actionable: verify and pin signed images before use — unverified external images are a supply-chain risk (see CWE-494: Download of Code Without Integrity Check). Also remove duplicate STANDARD declaration to avoid accidental override.
Deployment-mode handling
utilities/general.py, utilities/inference_utils.py
Broadened deployment-mode checks to include STANDARD alongside existing RAW_DEPLOYMENT/SERVERLESS paths for label selection and service exposure logic. Ensure behavior change is intended for both label-based selection and exposure branching.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

🚥 Pre-merge checks | ✅ 2
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed Title clearly and specifically describes the main change: adding model car (OCI image) deployment tests for MLServer.
Description check ✅ Passed PR description covers all required template sections with clear summary, related JIRA ticket, testing status, and checklist items.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🧹 Nitpick comments (1)
tests/model_serving/model_runtime/mlserver/model_car/test_mlserver_model_car.py (1)

119-125: Select a ready predictor pod instead of the first listed pod.

Line 125 uses pods[0], which is non-deterministic during restarts/rollouts and can produce flaky inference failures.

Proposed fix
         pods = get_pods_by_isvc_label(
             client=mlserver_model_car_inference_service.client,
             isvc=mlserver_model_car_inference_service,
         )
         if not pods:
             raise RuntimeError(f"No pods found for InferenceService {mlserver_model_car_inference_service.name}")
-        pod = pods[0]
+        ready_pods = [
+            p
+            for p in pods
+            if any(
+                cs.ready
+                for cs in (getattr(p.instance.status, "containerStatuses", None) or [])
+            )
+        ]
+        if not ready_pods:
+            raise RuntimeError(
+                f"No ready pods found for InferenceService {mlserver_model_car_inference_service.name}"
+            )
+        pod = ready_pods[0]

As per coding guidelines, "REVIEW PRIORITIES: 3. Bug-prone patterns and error handling gaps".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@tests/model_serving/model_runtime/mlserver/model_car/test_mlserver_model_car.py`
around lines 119 - 125, The test currently picks pods[0] which is
non-deterministic; change it to choose a ready predictor pod from the list
returned by get_pods_by_isvc_label (mlserver_model_car_inference_service) by
filtering pods for readiness (e.g., pod.status.phase == "Running" and
pod.status.conditions contains condition type "Ready" == "True" or a
container_status with ready==True) and/or label identifying the predictor
container, then assign that ready pod to pod; if no ready predictor pod is
found, raise a clear RuntimeError stating no ready predictor pod for the
InferenceService instead of using pods[0].
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@tests/model_serving/model_runtime/mlserver/conftest.py`:
- Around line 169-173: The test fixture currently allows storage_uri to be None
which delays the failure to create_isvc and obscures the real error; add an
explicit validation right after storage_uri = params.get("storage-uri") to raise
a ValueError (or similar) when storage_uri is missing/empty, mirroring the
existing model_format check so callers fail fast before invoking create_isvc.
- Around line 170-184: The code reads params["deployment-mode"] into
deployment_mode which misses callers using "deployment_type" and can silently
mis-handle Standard deployments; change the lookup to normalize both keys (e.g.,
check "deployment-mode" then "deployment_type" or map/alias them) before passing
deployment_mode into create_isvc, and change the wait_for_predictor_pods default
in the create_isvc call to a deterministic True (use
params.get("wait_for_predictor_pods", True)) so pod readiness is awaited; update
references to the variables deployment_mode and the create_isvc call sites
accordingly.

In `@tests/model_serving/model_runtime/mlserver/utils.py`:
- Around line 206-220: Validate model_format_name before using getattr: ensure
model_format_name is a non-empty string and that ModelCarImage has the dynamic
attribute f"MLSERVER_{model_format_name.upper()}" (use hasattr or catch
AttributeError) and if missing raise a clear ValueError mentioning the
unsupported model_format_name and the expected constant name; then proceed to
fetch the storage_uri via getattr(ModelCarImage, ...) and build the config as
before.

In `@utilities/constants.py`:
- Around line 300-304: Constants MLSERVER_SKLEARN, MLSERVER_XGBOOST,
MLSERVER_LIGHTGBM currently point to a personal quay.io namespace and
MLSERVER_ONNX is empty; replace these hardcoded URIs with
organization-owned/mirrored registry URIs (e.g., change quay.io/jooholee/... to
the org mirror like quay.io/opendatahub-io/...) or make them configurable via
environment variables and fall back to sensible defaults, and ensure
MLSERVER_ONNX is populated before use (or add a runtime guard in the code that
references MLSERVER_ONNX to raise a clear error if it is empty). Update the
constants MLSERVER_SKLEARN, MLSERVER_XGBOOST, MLSERVER_LIGHTGBM, and
MLSERVER_ONNX and/or add validation logic where these symbols are consumed to
prevent CI/production breakage if the values are missing or still pointing at
personal namespaces.

---

Nitpick comments:
In
`@tests/model_serving/model_runtime/mlserver/model_car/test_mlserver_model_car.py`:
- Around line 119-125: The test currently picks pods[0] which is
non-deterministic; change it to choose a ready predictor pod from the list
returned by get_pods_by_isvc_label (mlserver_model_car_inference_service) by
filtering pods for readiness (e.g., pod.status.phase == "Running" and
pod.status.conditions contains condition type "Ready" == "True" or a
container_status with ready==True) and/or label identifying the predictor
container, then assign that ready pod to pod; if no ready predictor pod is
found, raise a clear RuntimeError stating no ready predictor pod for the
InferenceService instead of using pods[0].

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository YAML (base), Central YAML (inherited), Organization UI (inherited)

Review profile: CHILL

Plan: Pro

Run ID: cf351258-cc36-4c0f-9b97-1de88a52e681

📥 Commits

Reviewing files that changed from the base of the PR and between 73cc4d5 and 87717f5.

📒 Files selected for processing (11)
  • tests/model_serving/model_runtime/mlserver/conftest.py
  • tests/model_serving/model_runtime/mlserver/model_car/__init__.py
  • tests/model_serving/model_runtime/mlserver/model_car/__snapshots__/test_mlserver_model_car/TestMLServerModelCar.test_mlserver_model_car_inference[lightgbm-raw-deployment-modelcar].json
  • tests/model_serving/model_runtime/mlserver/model_car/__snapshots__/test_mlserver_model_car/TestMLServerModelCar.test_mlserver_model_car_inference[lightgbm-raw-deployment-modelcar_text_type].json
  • tests/model_serving/model_runtime/mlserver/model_car/__snapshots__/test_mlserver_model_car/TestMLServerModelCar.test_mlserver_model_car_inference[sklearn-raw-deployment-modelcar].json
  • tests/model_serving/model_runtime/mlserver/model_car/__snapshots__/test_mlserver_model_car/TestMLServerModelCar.test_mlserver_model_car_inference[xgboost-raw-deployment-modelcar].json
  • tests/model_serving/model_runtime/mlserver/model_car/test_mlserver_model_car.py
  • tests/model_serving/model_runtime/mlserver/utils.py
  • utilities/constants.py
  • utilities/general.py
  • utilities/inference_utils.py

Comment on lines +169 to +173
storage_uri = params.get("storage-uri")
deployment_mode = params.get("deployment-mode", KServeDeploymentType.RAW_DEPLOYMENT)
model_format = params.get("model-format")
if not model_format:
raise ValueError("model-format is required in params")
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Fail fast when storage_uri is missing.

Line 169 allows storage_uri=None and defers failure to create_isvc, which obscures the root cause.

Proposed fix
-    storage_uri = params.get("storage-uri")
+    storage_uri = params.get("storage_uri") or params.get("storage-uri")
     deployment_mode = params.get("deployment-mode", KServeDeploymentType.RAW_DEPLOYMENT)
     model_format = params.get("model-format")
+    if not storage_uri:
+        raise ValueError("storage_uri (or storage-uri) is required in params")
     if not model_format:
         raise ValueError("model-format is required in params")

As per coding guidelines, "REVIEW PRIORITIES: 3. Bug-prone patterns and error handling gaps".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/model_serving/model_runtime/mlserver/conftest.py` around lines 169 -
173, The test fixture currently allows storage_uri to be None which delays the
failure to create_isvc and obscures the real error; add an explicit validation
right after storage_uri = params.get("storage-uri") to raise a ValueError (or
similar) when storage_uri is missing/empty, mirroring the existing model_format
check so callers fail fast before invoking create_isvc.

Comment on lines +170 to +184
deployment_mode = params.get("deployment-mode", KServeDeploymentType.RAW_DEPLOYMENT)
model_format = params.get("model-format")
if not model_format:
raise ValueError("model-format is required in params")

with create_isvc(
client=admin_client,
name=f"{model_format}-modelcar",
namespace=model_namespace.name,
runtime=mlserver_serving_runtime.name,
storage_uri=storage_uri,
model_format=model_format,
deployment_mode=deployment_mode,
external_route=params.get("enable_external_route"),
wait_for_predictor_pods=params.get("wait_for_predictor_pods", False),
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Normalize deployment-mode keys and avoid non-deterministic pod readiness defaults.

Line 170 uses deployment-mode, but related fixtures consume deployment_type; this can silently ignore Standard mode inputs. Also, Line 184 defaults wait_for_predictor_pods to False, which makes the test path race-prone.

Proposed fix
-    deployment_mode = params.get("deployment-mode", KServeDeploymentType.RAW_DEPLOYMENT)
+    deployment_mode = (
+        params.get("deployment_type")
+        or params.get("deployment-mode")
+        or KServeDeploymentType.RAW_DEPLOYMENT
+    )
@@
-        external_route=params.get("enable_external_route"),
-        wait_for_predictor_pods=params.get("wait_for_predictor_pods", False),
+        external_route=params.get("enable_external_route", False),
+        wait_for_predictor_pods=params.get("wait_for_predictor_pods", True),

As per coding guidelines, "REVIEW PRIORITIES: 2. Architectural issues and anti-patterns" and "3. Bug-prone patterns and error handling gaps".

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
deployment_mode = params.get("deployment-mode", KServeDeploymentType.RAW_DEPLOYMENT)
model_format = params.get("model-format")
if not model_format:
raise ValueError("model-format is required in params")
with create_isvc(
client=admin_client,
name=f"{model_format}-modelcar",
namespace=model_namespace.name,
runtime=mlserver_serving_runtime.name,
storage_uri=storage_uri,
model_format=model_format,
deployment_mode=deployment_mode,
external_route=params.get("enable_external_route"),
wait_for_predictor_pods=params.get("wait_for_predictor_pods", False),
deployment_mode = (
params.get("deployment_type")
or params.get("deployment-mode")
or KServeDeploymentType.RAW_DEPLOYMENT
)
model_format = params.get("model-format")
if not model_format:
raise ValueError("model-format is required in params")
with create_isvc(
client=admin_client,
name=f"{model_format}-modelcar",
namespace=model_namespace.name,
runtime=mlserver_serving_runtime.name,
storage_uri=storage_uri,
model_format=model_format,
deployment_mode=deployment_mode,
external_route=params.get("enable_external_route", False),
wait_for_predictor_pods=params.get("wait_for_predictor_pods", True),
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/model_serving/model_runtime/mlserver/conftest.py` around lines 170 -
184, The code reads params["deployment-mode"] into deployment_mode which misses
callers using "deployment_type" and can silently mis-handle Standard
deployments; change the lookup to normalize both keys (e.g., check
"deployment-mode" then "deployment_type" or map/alias them) before passing
deployment_mode into create_isvc, and change the wait_for_predictor_pods default
in the create_isvc call to a deterministic True (use
params.get("wait_for_predictor_pods", True)) so pod readiness is awaited; update
references to the variables deployment_mode and the create_isvc call sites
accordingly.

Comment on lines +206 to +220
if modelcar:
from utilities.constants import ModelCarImage

# Get OCI image URI from ModelCarImage constant
storage_uri = getattr(ModelCarImage, f"MLSERVER_{model_format_name.upper()}")

def get_model_namespace_dict(model_format_name: str, deployment_type: str) -> dict[str, str]:
config: dict[str, Any] = {
"storage-uri": storage_uri,
"model-format": model_format_name,
}

if env_variables:
config["model_env_variables"] = env_variables

return config
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Missing validation for model_format_name before dynamic attribute lookup.

getattr(ModelCarImage, f"MLSERVER_{model_format_name.upper()}") will raise AttributeError with a cryptic message if model_format_name doesn't have a corresponding constant. Add validation or a meaningful error.

Proposed fix
     if modelcar:
         from utilities.constants import ModelCarImage
 
-        # Get OCI image URI from ModelCarImage constant
-        storage_uri = getattr(ModelCarImage, f"MLSERVER_{model_format_name.upper()}")
+        # Get OCI image URI from ModelCarImage constant
+        attr_name = f"MLSERVER_{model_format_name.upper()}"
+        if not hasattr(ModelCarImage, attr_name):
+            raise ValueError(f"Unsupported model format for modelcar: {model_format_name}. "
+                           f"No ModelCarImage.{attr_name} constant defined.")
+        storage_uri = getattr(ModelCarImage, attr_name)
+        if not storage_uri:
+            raise ValueError(f"ModelCarImage.{attr_name} is empty. OCI URI not yet configured.")
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if modelcar:
from utilities.constants import ModelCarImage
# Get OCI image URI from ModelCarImage constant
storage_uri = getattr(ModelCarImage, f"MLSERVER_{model_format_name.upper()}")
def get_model_namespace_dict(model_format_name: str, deployment_type: str) -> dict[str, str]:
config: dict[str, Any] = {
"storage-uri": storage_uri,
"model-format": model_format_name,
}
if env_variables:
config["model_env_variables"] = env_variables
return config
if modelcar:
from utilities.constants import ModelCarImage
# Get OCI image URI from ModelCarImage constant
attr_name = f"MLSERVER_{model_format_name.upper()}"
if not hasattr(ModelCarImage, attr_name):
raise ValueError(f"Unsupported model format for modelcar: {model_format_name}. "
f"No ModelCarImage.{attr_name} constant defined.")
storage_uri = getattr(ModelCarImage, attr_name)
if not storage_uri:
raise ValueError(f"ModelCarImage.{attr_name} is empty. OCI URI not yet configured.")
config: dict[str, Any] = {
"storage-uri": storage_uri,
"model-format": model_format_name,
}
if env_variables:
config["model_env_variables"] = env_variables
return config
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/model_serving/model_runtime/mlserver/utils.py` around lines 206 - 220,
Validate model_format_name before using getattr: ensure model_format_name is a
non-empty string and that ModelCarImage has the dynamic attribute
f"MLSERVER_{model_format_name.upper()}" (use hasattr or catch AttributeError)
and if missing raise a clear ValueError mentioning the unsupported
model_format_name and the expected constant name; then proceed to fetch the
storage_uri via getattr(ModelCarImage, ...) and build the config as before.

Comment on lines +300 to +304
# MLServer model car images - update URIs when images are available
MLSERVER_SKLEARN: str = "oci://quay.io/jooholee/mlserver-sklearn@sha256:e0b0b1d17ae852bccccce35df7b0126d24f04626bb7fa41e9f19039d6b285340"
MLSERVER_XGBOOST: str = "oci://quay.io/jooholee/mlserver-xgboost@sha256:de50a0cd9c36fe300b3bffe25875daef886da9b0d0f8b5776a3b5395860ec785"
MLSERVER_LIGHTGBM: str = "oci://quay.io/jooholee/mlserver-lightgbm@sha256:ce80860b4427348f580f95f9826b1fdfb105e4240eacf69fb143883f4aa667f4"
MLSERVER_ONNX: str = "" # TODO: Add OCI URI when image ready
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check if any code currently references MLSERVER_ONNX
rg -n "MLSERVER_ONNX" --type=py

Repository: opendatahub-io/opendatahub-tests

Length of output: 168


🏁 Script executed:

rg -n "quay\.io|registry|OCI.*URI" --type=py -A 1 -B 1

Repository: opendatahub-io/opendatahub-tests

Length of output: 50389


Personal quay.io namespace creates supply-chain and availability risk.

Images in quay.io/jooholee/ (MLSERVER_SKLEARN, MLSERVER_XGBOOST, MLSERVER_LIGHTGBM) and similar personal namespaces like quay.io/mwaykole/ are not suitable for production or CI pipelines. If the personal account is deleted or access revoked, these images become unavailable. Mirror to an organization-owned registry (e.g., quay.io/opendatahub-io/) before these constants are actively used in tests.

The empty MLSERVER_ONNX value requires population before any code path references it. The TODO comment indicates awareness, but this must be resolved before the constant is used.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@utilities/constants.py` around lines 300 - 304, Constants MLSERVER_SKLEARN,
MLSERVER_XGBOOST, MLSERVER_LIGHTGBM currently point to a personal quay.io
namespace and MLSERVER_ONNX is empty; replace these hardcoded URIs with
organization-owned/mirrored registry URIs (e.g., change quay.io/jooholee/... to
the org mirror like quay.io/opendatahub-io/...) or make them configurable via
environment variables and fall back to sensible defaults, and ensure
MLSERVER_ONNX is populated before use (or add a runtime guard in the code that
references MLSERVER_ONNX to raise a clear error if it is empty). Update the
constants MLSERVER_SKLEARN, MLSERVER_XGBOOST, MLSERVER_LIGHTGBM, and
MLSERVER_ONNX and/or add validation logic where these symbols are consumed to
prevent CI/production breakage if the values are missing or still pointing at
personal namespaces.

@Jooho
Copy link
Copy Markdown
Contributor Author

Jooho commented Mar 31, 2026

- 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>
@Jooho Jooho force-pushed the mlserver/modelcar_test branch from 2cc3167 to 56d9f09 Compare March 31, 2026 23:02
@Jooho Jooho force-pushed the mlserver/modelcar_test branch from 56d9f09 to 84b53ab Compare March 31, 2026 23:03
pre-commit-ci bot and others added 4 commits March 31, 2026 19:06
for more information, see https://pre-commit.ci

Signed-off-by: Jooho Lee <jlee@redhat.com>
Signed-off-by: Jooho Lee <jlee@redhat.com>
Signed-off-by: Jooho Lee <jlee@redhat.com>
Signed-off-by: Jooho Lee <jlee@redhat.com>
@Jooho Jooho force-pushed the mlserver/modelcar_test branch from b23d7ef to a875c9c Compare March 31, 2026 23:06
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
tests/model_serving/model_runtime/mlserver/utils.py (1)

272-275: ⚠️ Potential issue | 🟠 Major

Fail fast for unsupported deployment type instead of returning empty config.

Lines [272]-[275] return {} for unknown deployment_type, which defers errors and obscures root cause. Raise ValueError immediately with supported options.

Proposed fix
 def get_deployment_config_dict(
     model_format_name: str,
     deployment_type: str = RAW_DEPLOYMENT_TYPE,
 ) -> dict[str, str]:
@@
-    deployment_config_dict = {}
-
-    if deployment_type == RAW_DEPLOYMENT_TYPE:
-        deployment_config_dict = {"name": model_format_name, **BASE_RAW_DEPLOYMENT_CONFIG}
-
-    return deployment_config_dict
+    if deployment_type == RAW_DEPLOYMENT_TYPE:
+        return {"name": model_format_name, **BASE_RAW_DEPLOYMENT_CONFIG}
+
+    raise ValueError(
+        f"Unsupported deployment_type: {deployment_type}. "
+        f"Supported values: ({RAW_DEPLOYMENT_TYPE},)"
+    )
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/model_serving/model_runtime/mlserver/utils.py` around lines 272 - 275,
The code currently falls through and returns an empty deployment_config_dict
when deployment_type is not recognized; change this to fail fast by raising a
ValueError that names the unsupported deployment_type and lists the supported
options (e.g., RAW_DEPLOYMENT_TYPE and any others expected) instead of returning
{} — update the branch around
RAW_DEPLOYMENT_TYPE/model_format_name/BASE_RAW_DEPLOYMENT_CONFIG in
tests/model_serving/model_runtime/mlserver/utils.py to raise ValueError with a
clear message when deployment_type is unknown.
♻️ Duplicate comments (1)
tests/model_serving/model_runtime/mlserver/utils.py (1)

206-211: ⚠️ Potential issue | 🟡 Minor

Validate ModelCarImage constant existence before dynamic lookup.

Line [210] does an unchecked dynamic getattr, so unsupported model_format_name fails with an opaque AttributeError. Validate and raise a clear ValueError that includes the expected constant name.

Proposed fix
 if modelcar:
     from utilities.constants import ModelCarImage

-    # Get OCI image URI from ModelCarImage constant
-    storage_uri = getattr(ModelCarImage, f"MLSERVER_{model_format_name.upper()}")
+    # Get OCI image URI from ModelCarImage constant
+    attr_name = f"MLSERVER_{model_format_name.upper()}"
+    if not model_format_name.strip():
+        raise ValueError("model_format_name must be a non-empty string")
+    if not hasattr(ModelCarImage, attr_name):
+        raise ValueError(
+            f"Unsupported model format for modelcar: {model_format_name}. "
+            f"Expected ModelCarImage.{attr_name}"
+        )
+    storage_uri = getattr(ModelCarImage, attr_name)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/model_serving/model_runtime/mlserver/utils.py` around lines 206 - 211,
The dynamic lookup using getattr(ModelCarImage,
f"MLSERVER_{model_format_name.upper()}") can raise an opaque AttributeError for
unsupported formats; before calling getattr in the modelcar branch, compute
const_name = f"MLSERVER_{model_format_name.upper()}" and check
hasattr(ModelCarImage, const_name); if missing, raise a clear ValueError
referencing model_format_name and the expected constant name (const_name),
otherwise assign storage_uri = getattr(ModelCarImage, const_name).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@tests/model_serving/model_runtime/mlserver/utils.py`:
- Around line 272-275: The code currently falls through and returns an empty
deployment_config_dict when deployment_type is not recognized; change this to
fail fast by raising a ValueError that names the unsupported deployment_type and
lists the supported options (e.g., RAW_DEPLOYMENT_TYPE and any others expected)
instead of returning {} — update the branch around
RAW_DEPLOYMENT_TYPE/model_format_name/BASE_RAW_DEPLOYMENT_CONFIG in
tests/model_serving/model_runtime/mlserver/utils.py to raise ValueError with a
clear message when deployment_type is unknown.

---

Duplicate comments:
In `@tests/model_serving/model_runtime/mlserver/utils.py`:
- Around line 206-211: The dynamic lookup using getattr(ModelCarImage,
f"MLSERVER_{model_format_name.upper()}") can raise an opaque AttributeError for
unsupported formats; before calling getattr in the modelcar branch, compute
const_name = f"MLSERVER_{model_format_name.upper()}" and check
hasattr(ModelCarImage, const_name); if missing, raise a clear ValueError
referencing model_format_name and the expected constant name (const_name),
otherwise assign storage_uri = getattr(ModelCarImage, const_name).

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository YAML (base), Central YAML (inherited), Organization UI (inherited)

Review profile: CHILL

Plan: Pro

Run ID: 38c6c746-6970-4469-8180-07916196ebad

📥 Commits

Reviewing files that changed from the base of the PR and between 2cc3167 and a0b0ec7.

📒 Files selected for processing (13)
  • tests/model_serving/model_runtime/mlserver/conftest.py
  • tests/model_serving/model_runtime/mlserver/model_car/README.md
  • tests/model_serving/model_runtime/mlserver/model_car/__init__.py
  • tests/model_serving/model_runtime/mlserver/model_car/__snapshots__/test_mlserver_model_car/TestMLServerModelCar.test_mlserver_model_car_inference[lightgbm-raw-deployment-modelcar].json
  • tests/model_serving/model_runtime/mlserver/model_car/__snapshots__/test_mlserver_model_car/TestMLServerModelCar.test_mlserver_model_car_inference[lightgbm-raw-deployment-modelcar_text_type].json
  • tests/model_serving/model_runtime/mlserver/model_car/__snapshots__/test_mlserver_model_car/TestMLServerModelCar.test_mlserver_model_car_inference[onnx-raw-deployment-modelcar].json
  • tests/model_serving/model_runtime/mlserver/model_car/__snapshots__/test_mlserver_model_car/TestMLServerModelCar.test_mlserver_model_car_inference[sklearn-raw-deployment-modelcar].json
  • tests/model_serving/model_runtime/mlserver/model_car/__snapshots__/test_mlserver_model_car/TestMLServerModelCar.test_mlserver_model_car_inference[xgboost-raw-deployment-modelcar].json
  • tests/model_serving/model_runtime/mlserver/model_car/test_mlserver_model_car.py
  • tests/model_serving/model_runtime/mlserver/utils.py
  • utilities/constants.py
  • utilities/general.py
  • utilities/inference_utils.py
✅ Files skipped from review due to trivial changes (8)
  • tests/model_serving/model_runtime/mlserver/model_car/snapshots/test_mlserver_model_car/TestMLServerModelCar.test_mlserver_model_car_inference[xgboost-raw-deployment-modelcar].json
  • tests/model_serving/model_runtime/mlserver/model_car/snapshots/test_mlserver_model_car/TestMLServerModelCar.test_mlserver_model_car_inference[lightgbm-raw-deployment-modelcar].json
  • tests/model_serving/model_runtime/mlserver/model_car/snapshots/test_mlserver_model_car/TestMLServerModelCar.test_mlserver_model_car_inference[lightgbm-raw-deployment-modelcar_text_type].json
  • tests/model_serving/model_runtime/mlserver/model_car/README.md
  • tests/model_serving/model_runtime/mlserver/model_car/snapshots/test_mlserver_model_car/TestMLServerModelCar.test_mlserver_model_car_inference[onnx-raw-deployment-modelcar].json
  • tests/model_serving/model_runtime/mlserver/conftest.py
  • tests/model_serving/model_runtime/mlserver/model_car/test_mlserver_model_car.py
  • utilities/constants.py
🚧 Files skipped from review as they are similar to previous changes (2)
  • utilities/general.py
  • tests/model_serving/model_runtime/mlserver/model_car/snapshots/test_mlserver_model_car/TestMLServerModelCar.test_mlserver_model_car_inference[sklearn-raw-deployment-modelcar].json

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants