Skip to content

Commit 50b3e59

Browse files
committed
ci: merge main branch
2 parents d8a0dcc + 8fece39 commit 50b3e59

File tree

5 files changed

+693
-629
lines changed

5 files changed

+693
-629
lines changed

tests/model_registry/conftest.py

Lines changed: 93 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,40 @@
11
import pytest
2+
import re
23
import schemathesis
34
from typing import Generator, Any
5+
from kubernetes.dynamic.exceptions import ResourceNotFoundError
6+
from ocp_resources.pod import Pod
47
from ocp_resources.secret import Secret
58
from ocp_resources.namespace import Namespace
69
from ocp_resources.service import Service
710
from ocp_resources.persistent_volume_claim import PersistentVolumeClaim
11+
from ocp_resources.data_science_cluster import DataScienceCluster
812
from ocp_resources.deployment import Deployment
13+
914
from ocp_resources.model_registry import ModelRegistry
15+
from ocp_resources.resource import ResourceEditor
16+
17+
from pytest import FixtureRequest
1018
from simple_logger.logger import get_logger
1119
from kubernetes.dynamic import DynamicClient
12-
20+
from pytest_testconfig import config as py_config
21+
from model_registry.types import RegisteredModel
22+
from tests.model_registry.constants import (
23+
MR_NAMESPACE,
24+
MR_OPERATOR_NAME,
25+
MR_INSTANCE_NAME,
26+
ISTIO_CONFIG_DICT,
27+
DB_RESOURCES_NAME,
28+
MR_DB_IMAGE_DIGEST,
29+
)
1330
from tests.model_registry.utils import get_endpoint_from_mr_service, get_mr_service_by_label
1431
from utilities.infra import create_ns
15-
from utilities.constants import Annotations, Protocols
16-
from .constants import MR_DB_IMAGE_DIGEST
32+
from utilities.constants import Annotations, Protocols, DscComponents
33+
from model_registry import ModelRegistry as ModelRegistryClient
1734

1835

1936
LOGGER = get_logger(name=__name__)
2037

21-
DB_RESOURCES_NAME: str = "model-registry-db"
22-
MR_INSTANCE_NAME: str = "model-registry"
23-
MR_OPERATOR_NAME: str = "model-registry-operator"
24-
MR_NAMESPACE: str = "rhoai-model-registries"
2538
DEFAULT_LABEL_DICT_DB: dict[str, str] = {
2639
Annotations.KubernetesIo.NAME: DB_RESOURCES_NAME,
2740
Annotations.KubernetesIo.INSTANCE: DB_RESOURCES_NAME,
@@ -30,20 +43,12 @@
3043

3144

3245
@pytest.fixture(scope="class")
33-
def model_registry_namespace(admin_client: DynamicClient) -> Generator[Namespace, Any, Any]:
34-
# This namespace should exist after Model Registry is enabled, but it can also be deleted
35-
# from the cluster and does not get reconciled. Fetch if it exists, create otherwise.
36-
ns = Namespace(name=MR_NAMESPACE, client=admin_client)
37-
if ns.exists:
46+
def model_registry_namespace(request: FixtureRequest, admin_client: DynamicClient) -> Generator[Namespace, Any, Any]:
47+
with create_ns(
48+
name=request.param.get("namespace_name", MR_NAMESPACE),
49+
admin_client=admin_client,
50+
) as ns:
3851
yield ns
39-
else:
40-
LOGGER.warning(f"{MR_NAMESPACE} namespace was not present, creating it")
41-
with create_ns(
42-
name=MR_NAMESPACE,
43-
admin_client=admin_client,
44-
teardown=False,
45-
) as ns:
46-
yield ns
4752

4853

4954
@pytest.fixture(scope="class")
@@ -259,10 +264,7 @@ def model_registry_instance(
259264
},
260265
grpc={},
261266
rest={},
262-
istio={
263-
"authProvider": "redhat-ods-applications-auth-provider",
264-
"gateway": {"grpc": {"tls": {}}, "rest": {"tls": {}}},
265-
},
267+
istio=ISTIO_CONFIG_DICT,
266268
mysql={
267269
"host": f"{model_registry_db_deployment.name}.{model_registry_db_deployment.namespace}.svc.cluster.local",
268270
"database": model_registry_db_secret.string_data["database-name"],
@@ -304,3 +306,70 @@ def generated_schema(model_registry_instance_rest_endpoint: str) -> Any:
304306
uri="https://raw.githubusercontent.com/kubeflow/model-registry/main/api/openapi/model-registry.yaml",
305307
base_url=f"https://{model_registry_instance_rest_endpoint}/",
306308
)
309+
310+
311+
@pytest.fixture(scope="class")
312+
def updated_dsc_component_state_scope_class(
313+
request: FixtureRequest,
314+
model_registry_namespace: Namespace,
315+
dsc_resource: DataScienceCluster,
316+
) -> Generator[DataScienceCluster, Any, Any]:
317+
original_components = dsc_resource.instance.spec.components
318+
with ResourceEditor(patches={dsc_resource: {"spec": {"components": request.param["component_patch"]}}}):
319+
for component_name in request.param["component_patch"]:
320+
dsc_resource.wait_for_condition(condition=DscComponents.COMPONENT_MAPPING[component_name], status="True")
321+
yield dsc_resource
322+
323+
for component_name, value in request.param["component_patch"].items():
324+
LOGGER.info(f"Waiting for component {component_name} to be updated.")
325+
if original_components[component_name]["managementState"] == DscComponents.ManagementState.MANAGED:
326+
dsc_resource.wait_for_condition(condition=DscComponents.COMPONENT_MAPPING[component_name], status="True")
327+
if (
328+
component_name == DscComponents.MODELREGISTRY
329+
and value.get("managementState") == DscComponents.ManagementState.MANAGED
330+
):
331+
# Since namespace specified in registriesNamespace is automatically created after setting
332+
# managementStateto Managed. We need to explicitly delete it on clean up.
333+
namespace = Namespace(name=value["registriesNamespace"], ensure_exists=True)
334+
if namespace:
335+
namespace.delete(wait=True)
336+
337+
338+
@pytest.fixture(scope="class")
339+
def model_registry_client(current_client_token: str, model_registry_instance_rest_endpoint: str) -> ModelRegistryClient:
340+
# address and port need to be split in the client instantiation
341+
server, port = model_registry_instance_rest_endpoint.split(":")
342+
return ModelRegistryClient(
343+
server_address=f"{Protocols.HTTPS}://{server}",
344+
port=port,
345+
author="opendatahub-test",
346+
user_token=current_client_token,
347+
is_secure=False,
348+
)
349+
350+
351+
@pytest.fixture(scope="class")
352+
def registered_model(request: FixtureRequest, model_registry_client: ModelRegistryClient) -> RegisteredModel:
353+
return model_registry_client.register_model(
354+
name=request.param.get("model_name"),
355+
uri=request.param.get("model_uri"),
356+
version=request.param.get("model_version"),
357+
description=request.param.get("model_description"),
358+
model_format_name=request.param.get("model_format"),
359+
model_format_version=request.param.get("model_format_version"),
360+
storage_key=request.param.get("model_storage_key"),
361+
storage_path=request.param.get("model_storage_path"),
362+
metadata=request.param.get("model_metadata"),
363+
)
364+
365+
366+
@pytest.fixture()
367+
def model_registry_operator_pod(admin_client: DynamicClient) -> Pod:
368+
model_registry_operator_pods = [
369+
pod
370+
for pod in Pod.get(dyn_client=admin_client, namespace=py_config["applications_namespace"])
371+
if re.match(MR_OPERATOR_NAME, pod.name)
372+
]
373+
if not model_registry_operator_pods:
374+
raise ResourceNotFoundError("Model registry operator pod not found")
375+
return model_registry_operator_pods[0]

tests/model_registry/constants.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,37 @@
1+
from typing import Any
2+
3+
from utilities.constants import ModelFormat
4+
5+
16
class ModelRegistryEndpoints:
27
REGISTERED_MODELS: str = "/api/model_registry/v1alpha3/registered_models"
38

49

10+
MR_NAMESPACE: str = "rhoai-model-registries"
11+
MR_OPERATOR_NAME: str = "model-registry-operator"
12+
MODEL_NAME: str = "my-model"
13+
MODEL_DICT: dict[str, Any] = {
14+
"model_name": MODEL_NAME,
15+
"model_uri": "https://storage-place.my-company.com",
16+
"model_version": "2.0.0",
17+
"model_description": "lorem ipsum",
18+
"model_format": ModelFormat.ONNX,
19+
"model_format_version": "1",
20+
"model_storage_key": "my-data-connection",
21+
"model_storage_path": "path/to/model",
22+
"model_metadata": {
23+
"int_key": 1,
24+
"bool_key": False,
25+
"float_key": 3.14,
26+
"str_key": "str_value",
27+
},
28+
}
29+
MR_INSTANCE_NAME: str = "model-registry"
30+
ISTIO_CONFIG_DICT: dict[str, Any] = {
31+
"authProvider": "redhat-ods-applications-auth-provider",
32+
"gateway": {"grpc": {"tls": {}}, "rest": {"tls": {}}},
33+
}
34+
DB_RESOURCES_NAME: str = "model-registry-db"
535
MR_DB_IMAGE_DIGEST: str = (
636
"public.ecr.aws/docker/library/mysql@sha256:9de9d54fecee6253130e65154b930978b1fcc336bcc86dfd06e89b72a2588ebe"
737
)

tests/model_registry/test_model_registry_creation.py

Lines changed: 62 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -3,79 +3,99 @@
33
from simple_logger.logger import get_logger
44

55
from ocp_resources.data_science_cluster import DataScienceCluster
6-
from utilities.constants import Protocols, DscComponents, ModelFormat
7-
from model_registry import ModelRegistry
8-
6+
from ocp_resources.pod import Pod
7+
from ocp_resources.namespace import Namespace
8+
from utilities.constants import DscComponents
9+
from tests.model_registry.constants import MR_NAMESPACE, MODEL_NAME, MODEL_DICT
10+
from model_registry import ModelRegistry as ModelRegistryClient
11+
from model_registry.types import RegisteredModel
12+
from kubernetes.dynamic import DynamicClient
913

1014
LOGGER = get_logger(name=__name__)
11-
MODEL_NAME: str = "my-model"
15+
16+
CUSTOM_NAMESPACE = "model-registry-custom-ns"
1217

1318

1419
@pytest.mark.parametrize(
15-
"updated_dsc_component_state",
20+
"model_registry_namespace, updated_dsc_component_state_scope_class",
1621
[
1722
pytest.param(
23+
{"namespace_name": CUSTOM_NAMESPACE},
24+
{
25+
"component_patch": {
26+
DscComponents.MODELREGISTRY: {
27+
"managementState": DscComponents.ManagementState.MANAGED,
28+
"registriesNamespace": CUSTOM_NAMESPACE,
29+
},
30+
}
31+
},
32+
),
33+
pytest.param(
34+
{"namespace_name": MR_NAMESPACE},
1835
{
19-
"component_name": DscComponents.MODELREGISTRY,
20-
"desired_state": DscComponents.ManagementState.MANAGED,
36+
"component_patch": {
37+
DscComponents.MODELREGISTRY: {
38+
"managementState": DscComponents.ManagementState.MANAGED,
39+
"registriesNamespace": MR_NAMESPACE,
40+
},
41+
},
2142
},
22-
)
43+
),
2344
],
2445
indirect=True,
2546
)
47+
@pytest.mark.usefixtures("model_registry_namespace", "updated_dsc_component_state_scope_class")
2648
class TestModelRegistryCreation:
2749
"""
2850
Tests the creation of a model registry. If the component is set to 'Removed' it will be switched to 'Managed'
2951
for the duration of this test module.
3052
"""
3153

3254
@pytest.mark.smoke
55+
@pytest.mark.parametrize(
56+
"registered_model",
57+
[
58+
pytest.param(
59+
MODEL_DICT,
60+
)
61+
],
62+
indirect=True,
63+
)
3364
def test_registering_model(
3465
self: Self,
35-
model_registry_instance_rest_endpoint: str,
36-
current_client_token: str,
37-
updated_dsc_component_state: DataScienceCluster,
66+
model_registry_client: ModelRegistryClient,
67+
registered_model: RegisteredModel,
3868
):
39-
# address and port need to be split in the client instantiation
40-
server, port = model_registry_instance_rest_endpoint.split(":")
41-
registry = ModelRegistry(
42-
server_address=f"{Protocols.HTTPS}://{server}",
43-
port=port,
44-
author="opendatahub-test",
45-
user_token=current_client_token,
46-
is_secure=False,
47-
)
48-
model = registry.register_model(
49-
name=MODEL_NAME,
50-
uri="https://storage-place.my-company.com",
51-
version="2.0.0",
52-
description="lorem ipsum",
53-
model_format_name=ModelFormat.ONNX,
54-
model_format_version="1",
55-
storage_key="my-data-connection",
56-
storage_path="path/to/model",
57-
metadata={
58-
"int_key": 1,
59-
"bool_key": False,
60-
"float_key": 3.14,
61-
"str_key": "str_value",
62-
},
63-
)
64-
registered_model = registry.get_registered_model(MODEL_NAME)
69+
model = model_registry_client.get_registered_model(MODEL_NAME)
6570
errors = []
6671
if not registered_model.id == model.id:
67-
errors.append(f"Unexpected id, received {registered_model.id}")
72+
errors.append(f"Unexpected id, received {model.id}")
6873
if not registered_model.name == model.name:
69-
errors.append(f"Unexpected name, received {registered_model.name}")
74+
errors.append(f"Unexpected name, received {model.name}")
7075
if not registered_model.description == model.description:
71-
errors.append(f"Unexpected description, received {registered_model.description}")
76+
errors.append(f"Unexpected description, received {model.description}")
7277
if not registered_model.owner == model.owner:
73-
errors.append(f"Unexpected owner, received {registered_model.owner}")
78+
errors.append(f"Unexpected owner, received {model.owner}")
7479
if not registered_model.state == model.state:
75-
errors.append(f"Unexpected state, received {registered_model.state}")
80+
errors.append(f"Unexpected state, received {model.state}")
7681

7782
assert not errors, "errors found in model registry response validation:\n{}".format("\n".join(errors))
7883

84+
def test_model_registry_operator_env(
85+
self,
86+
admin_client: DynamicClient,
87+
model_registry_namespace: Namespace,
88+
updated_dsc_component_state_scope_class: DataScienceCluster,
89+
model_registry_operator_pod: Pod,
90+
):
91+
namespace_env = []
92+
for container in model_registry_operator_pod.instance.spec.containers:
93+
for env in container.env:
94+
if env.name == "REGISTRIES_NAMESPACE" and env.value == model_registry_namespace.name:
95+
namespace_env.append({container.name: env})
96+
if not namespace_env:
97+
pytest.fail("Missing environment variable REGISTRIES_NAMESPACE")
98+
7999
# TODO: Edit a registered model
80100
# TODO: Add additional versions for a model
81101
# TODO: List all available models

0 commit comments

Comments
 (0)