Skip to content

Commit 833706b

Browse files
authored
feat: add image validation for trustyai operator and service (opendatahub-io#348)
* feat: add image validation for trustyai operator and service * feat: add image validation for trustyai operator and service * feat: add fixture for trustyai operator deployment * feat: add fixture for trustyai operator deployment * docs: add docs for service image validation
1 parent dd44827 commit 833706b

9 files changed

Lines changed: 158 additions & 12 deletions

File tree

tests/conftest.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
)
4343
from utilities.infra import update_configmap_data
4444
from utilities.minio import create_minio_data_connection_secret
45+
from utilities.operator_utils import get_csv_related_images
4546

4647
LOGGER = get_logger(name=__name__)
4748

@@ -517,3 +518,10 @@ def prometheus(admin_client: DynamicClient) -> Prometheus:
517518
), # TODO: Verify SSL with appropriate certs
518519
bearer_token=get_openshift_token(),
519520
)
521+
522+
523+
@pytest.fixture(scope="session")
524+
def related_images_refs(admin_client: DynamicClient) -> set[str]:
525+
related_images = get_csv_related_images(admin_client=admin_client)
526+
related_images_refs = {img["image"] for img in related_images}
527+
return related_images_refs

tests/model_explainability/conftest.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,12 @@
22

33
import pytest
44
from kubernetes.dynamic import DynamicClient
5+
from ocp_resources.config_map import ConfigMap
56
from ocp_resources.namespace import Namespace
67
from ocp_resources.persistent_volume_claim import PersistentVolumeClaim
8+
from pytest_testconfig import config as py_config
9+
10+
from tests.model_explainability.trustyai_service.trustyai_service_utils import TRUSTYAI_SERVICE_NAME
711

812

913
@pytest.fixture(scope="class")
@@ -19,3 +23,15 @@ def pvc_minio_namespace(
1923
size="10Gi",
2024
) as pvc:
2125
yield pvc
26+
27+
28+
@pytest.fixture(scope="session")
29+
def trustyai_operator_configmap(
30+
admin_client: DynamicClient,
31+
) -> ConfigMap:
32+
return ConfigMap(
33+
client=admin_client,
34+
namespace=py_config["applications_namespace"],
35+
name=f"{TRUSTYAI_SERVICE_NAME}-operator-config",
36+
ensure_exists=True,
37+
)

tests/model_explainability/trustyai_operator/__init__.py

Whitespace-only changes.
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import pytest
2+
from kubernetes.dynamic import DynamicClient
3+
from pytest_testconfig import config as py_config
4+
from ocp_resources.deployment import Deployment
5+
6+
from tests.model_explainability.trustyai_service.trustyai_service_utils import TRUSTYAI_SERVICE_NAME
7+
8+
9+
@pytest.fixture(scope="class")
10+
def trustyai_operator_deployment(admin_client: DynamicClient) -> Deployment:
11+
return Deployment(
12+
client=admin_client,
13+
name=f"{TRUSTYAI_SERVICE_NAME}-operator-controller-manager",
14+
namespace=py_config["applications_namespace"],
15+
ensure_exists=True,
16+
)
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import pytest
2+
from kubernetes.dynamic import DynamicClient
3+
from ocp_resources.config_map import ConfigMap
4+
from ocp_resources.deployment import Deployment
5+
6+
from tests.model_explainability.trustyai_operator.utils import validate_trustyai_operator_image
7+
8+
9+
@pytest.mark.smoke
10+
def test_validate_trustyai_operator_image(
11+
admin_client: DynamicClient,
12+
related_images_refs: set[str],
13+
trustyai_operator_configmap: ConfigMap,
14+
trustyai_operator_deployment: Deployment,
15+
):
16+
return validate_trustyai_operator_image(
17+
related_images_refs=related_images_refs,
18+
tai_operator_configmap_data=trustyai_operator_configmap.instance.data,
19+
tai_operator_deployment=trustyai_operator_deployment,
20+
)
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
from ocp_resources.deployment import Deployment
2+
3+
from utilities.general import validate_image_format
4+
5+
6+
def validate_trustyai_operator_image(
7+
related_images_refs: set[str], tai_operator_configmap_data: dict[str, str], tai_operator_deployment: Deployment
8+
) -> None:
9+
"""Validates the TrustyAI operator image.
10+
Checks if:
11+
- container image matches that of the operator configmap.
12+
- image is present in relatedImages of CSV.
13+
- image complies with OpenShift AI requirements i.e. sourced from registry.redhat.io and pinned w/o tags.
14+
15+
Args:
16+
related_images_refs (set[str]): set of related image refs from the RHOAI CSV
17+
tai_operator_configmap_data (dict[str, str]): TrustyAI configmap data
18+
tai_operator_deployment (Deployment): TrustyAI deployment object
19+
20+
Returns:
21+
None
22+
23+
Raises:
24+
AssertionError: If any of the related images references are not present or invalid.
25+
"""
26+
tai_operator_image = tai_operator_deployment.instance.spec.template.spec.containers[0].image
27+
assert tai_operator_image == tai_operator_configmap_data["trustyaiOperatorImage"]
28+
assert tai_operator_image in related_images_refs
29+
image_valid, error_message = validate_image_format(image=tai_operator_image)
30+
assert image_valid, error_message

tests/model_explainability/trustyai_service/service/test_trustyai_service.py

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import pytest
22
from ocp_resources.namespace import Namespace
3+
from ocp_resources.trustyai_service import TrustyAIService
34

45
from tests.model_explainability.trustyai_service.constants import DRIFT_BASE_DATA_PATH
56
from tests.model_explainability.trustyai_service.trustyai_service_utils import (
@@ -9,7 +10,10 @@
910
verify_trustyai_service_metric_scheduling_request,
1011
verify_trustyai_service_metric_delete_request,
1112
)
12-
from tests.model_explainability.trustyai_service.utils import validate_trustyai_service_db_conn_failure
13+
from tests.model_explainability.trustyai_service.utils import (
14+
validate_trustyai_service_db_conn_failure,
15+
validate_trustyai_service_images,
16+
)
1317
from utilities.constants import MinIo
1418
from utilities.manifests.openvino import OPENVINO_KSERVE_INFERENCE_CONFIG
1519

@@ -146,3 +150,29 @@ def test_drift_metric_delete_multiple_ns(
146150
token=current_client_token,
147151
metric_name=TrustyAIServiceMetrics.Drift.MEANSHIFT,
148152
)
153+
154+
155+
@pytest.mark.parametrize(
156+
"model_namespace",
157+
[
158+
pytest.param(
159+
{"name": "test-validate-trustyai-service-images"},
160+
)
161+
],
162+
indirect=True,
163+
)
164+
@pytest.mark.smoke
165+
def test_validate_trustyai_service_image(
166+
admin_client,
167+
model_namespace: Namespace,
168+
related_images_refs: set[str],
169+
trustyai_service_with_pvc_storage: TrustyAIService,
170+
trustyai_operator_configmap,
171+
):
172+
return validate_trustyai_service_images(
173+
client=admin_client,
174+
related_images_refs=related_images_refs,
175+
model_namespace=model_namespace,
176+
label_selector=f"app.kubernetes.io/instance={trustyai_service_with_pvc_storage.name}",
177+
trustyai_operator_configmap=trustyai_operator_configmap,
178+
)

tests/model_explainability/trustyai_service/utils.py

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
import re
33

44
from kubernetes.dynamic import DynamicClient
5+
from ocp_resources.config_map import ConfigMap
56
from ocp_resources.deployment import Deployment
67
from ocp_resources.mariadb_operator import MariadbOperator
78
from ocp_resources.maria_db import MariaDB
@@ -19,6 +20,7 @@
1920
from timeout_sampler import retry
2021

2122
from utilities.exceptions import TooManyPodsError, UnexpectedFailureError
23+
from utilities.general import wait_for_pods_by_labels, validate_container_images
2224

2325
LOGGER = get_logger(name=__name__)
2426

@@ -245,3 +247,38 @@ def create_isvc_getter_token_secret(
245247
type="kubernetes.io/service-account-token",
246248
) as secret:
247249
yield secret
250+
251+
252+
def validate_trustyai_service_images(
253+
client: DynamicClient,
254+
related_images_refs: set[str],
255+
model_namespace: Namespace,
256+
label_selector: str,
257+
trustyai_operator_configmap: ConfigMap,
258+
) -> None:
259+
"""Validates trustyai service images against a set of related images.
260+
261+
Args:
262+
client: DynamicClient: The Kubernetes dynamic client.
263+
related_images_refs: list[str]: Related images references from RHOAI CSV.
264+
model_namespace: Namespace: namespace to run the test against.
265+
label_selector: str: Label selector string to get the trustyai pod.
266+
trustyai_operator_configmap: ConfigMap: The trustyai operator configmap.
267+
268+
Returns:
269+
None
270+
271+
Raises:
272+
AssertionError: If any of the related images references are not present or invalid.
273+
"""
274+
tai_image_refs = set(
275+
v
276+
for k, v in trustyai_operator_configmap.instance.data.items()
277+
if k in ["oauthProxyImage", "trustyaiServiceImage"]
278+
)
279+
trustyai_service_pod = wait_for_pods_by_labels(
280+
admin_client=client, namespace=model_namespace.name, label_selector=label_selector, expected_num_pods=1
281+
)[0]
282+
validation_errors = validate_container_images(pod=trustyai_service_pod, valid_image_refs=tai_image_refs)
283+
assert len(validation_errors) == 0, validation_errors
284+
assert tai_image_refs.issubset(related_images_refs), "TrustyAI service container images are not present in CSV."

tests/model_registry/image_validation/conftest.py

Lines changed: 0 additions & 11 deletions
This file was deleted.

0 commit comments

Comments
 (0)