Skip to content

Commit d308ab1

Browse files
authored
Merge branch 'opendatahub-io:main' into main
2 parents bd99110 + c19f07d commit d308ab1

37 files changed

Lines changed: 2370 additions & 791 deletions

.pre-commit-config.yaml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ repos:
3535
- id: detect-secrets
3636

3737
- repo: https://github.com/astral-sh/ruff-pre-commit
38-
rev: v0.11.11
38+
rev: v0.11.13
3939
hooks:
4040
- id: ruff
4141
- id: ruff-format
@@ -49,12 +49,12 @@ repos:
4949
# - id: renovate-config-validator
5050

5151
- repo: https://github.com/gitleaks/gitleaks
52-
rev: v8.26.0
52+
rev: v8.27.2
5353
hooks:
5454
- id: gitleaks
5555

5656
- repo: https://github.com/pre-commit/mirrors-mypy
57-
rev: v1.15.0
57+
rev: v1.16.0
5858
hooks:
5959
- id: mypy
6060
additional_dependencies: ["types-PyYAML", "types-requests"]

conftest.py

Lines changed: 34 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,10 @@
3333
get_base_dir,
3434
)
3535

36+
from kubernetes.dynamic import DynamicClient
37+
from utilities.infra import get_operator_distribution, get_dsci_applications_namespace, get_data_science_cluster
38+
from ocp_resources.resource import get_client
39+
3640
LOGGER = logging.getLogger(name=__name__)
3741
BASIC_LOGGER = logging.getLogger(name="basic")
3842

@@ -233,6 +237,36 @@ def pytest_sessionstart(session: Session) -> None:
233237
path=must_gather_dict["must_gather_base_directory"],
234238
ignore_errors=True,
235239
)
240+
config = session.config
241+
if config.getoption("--collect-only") or config.getoption("--setup-plan"):
242+
LOGGER.info("Skipping global config update for collect-only or setup-plan")
243+
return
244+
updated_global_config(admin_client=get_client())
245+
246+
247+
def updated_global_config(admin_client: DynamicClient) -> None:
248+
"""
249+
Updates the global config with the distribution, applications namespace, and model registry namespace.
250+
Args:
251+
admin_client: The admin client to use to get resources.
252+
Returns:
253+
None
254+
"""
255+
distribution = get_operator_distribution(client=admin_client)
256+
if distribution == "Open Data Hub":
257+
py_config["distribution"] = "upstream"
258+
259+
elif distribution.startswith("OpenShift AI"):
260+
py_config["distribution"] = "downstream"
261+
else:
262+
import pytest
263+
264+
pytest.exit(f"Unknown distribution: {distribution}")
265+
266+
py_config["applications_namespace"] = get_dsci_applications_namespace(client=admin_client)
267+
py_config["model_registry_namespace"] = get_data_science_cluster(
268+
client=admin_client
269+
).instance.spec.components.modelregistry.registriesNamespace
236270

237271

238272
def pytest_fixture_setup(fixturedef: FixtureDef[Any], request: FixtureRequest) -> None:
@@ -276,9 +310,6 @@ def pytest_runtest_setup(item: Item) -> None:
276310
elif KServeDeploymentType.MODEL_MESH.lower() in item.keywords:
277311
item.fixturenames.insert(0, "enabled_modelmesh_in_dsc")
278312

279-
# The above fixtures require the global config to be updated before being called
280-
item.fixturenames.insert(0, "updated_global_config")
281-
282313

283314
def pytest_runtest_call(item: Item) -> None:
284315
BASIC_LOGGER.info(f"{separator(symbol_='-', val='CALL')}")

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ dependencies = [
6363
"timeout-sampler>=1.0.6",
6464
"shortuuid>=1.0.13",
6565
"jira>=3.8.0",
66-
"openshift-python-wrapper>=11.0.50",
66+
"openshift-python-wrapper>=11.0.57",
6767
"semver>=3.0.4",
6868
"sqlalchemy>=2.0.40",
6969
"pytest-order>=1.3.0",

pytest.ini

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ markers =
1717
post_upgrade: Mark tests which should be run after upgrading the product.
1818
fuzzer: Mark tests that use fuzzing and are probably going to generate unanticipated failures.
1919
ocp_interop: Interop testing with Openshift.
20+
downstream_only: Tests that are specific to downstream
2021

2122
# Model server
2223
modelmesh: Mark tests which are model mesh tests
@@ -25,6 +26,7 @@ markers =
2526
minio: Mark tests which are using MinIO storage
2627
tls: Mark tests which are testing TLS
2728
metrics: Mark tests which are testing metrics
29+
kueue: Mark tests which are testing Kueue
2830

2931
addopts =
3032
-s

schemathesis.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
base-url = "https://${API_HOST}"

tests/conftest.py

Lines changed: 0 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -30,11 +30,8 @@
3030
from utilities.infra import (
3131
verify_cluster_sanity,
3232
create_ns,
33-
get_dsci_applications_namespace,
34-
get_operator_distribution,
3533
login_with_user_password,
3634
get_openshift_token,
37-
get_data_science_cluster,
3835
)
3936
from utilities.constants import (
4037
AcceleratorType,
@@ -65,23 +62,6 @@ def tests_tmp_dir(request: FixtureRequest, tmp_path_factory: TempPathFactory) ->
6562
shutil.rmtree(path=str(tests_tmp_path), ignore_errors=True)
6663

6764

68-
@pytest.fixture(scope="session")
69-
def updated_global_config(request: FixtureRequest, admin_client: DynamicClient) -> None:
70-
distribution = get_operator_distribution(client=admin_client)
71-
if distribution == "Open Data Hub":
72-
py_config["distribution"] = "upstream"
73-
74-
elif distribution.startswith("OpenShift AI"):
75-
py_config["distribution"] = "downstream"
76-
else:
77-
pytest.exit(f"Unknown distribution: {distribution}")
78-
79-
py_config["applications_namespace"] = get_dsci_applications_namespace(client=admin_client)
80-
py_config["model_registry_namespace"] = get_data_science_cluster(
81-
client=admin_client
82-
).instance.spec.components.modelregistry.registriesNamespace
83-
84-
8565
@pytest.fixture(scope="session")
8666
def current_client_token(admin_client: DynamicClient) -> str:
8767
return get_openshift_token()

tests/model_registry/conftest.py

Lines changed: 80 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1+
import os
12
import pytest
3+
from pytest import Config
24
import schemathesis
35
from typing import Generator, Any
46
from ocp_resources.pod import Pod
@@ -9,7 +11,7 @@
911
from ocp_resources.data_science_cluster import DataScienceCluster
1012
from ocp_resources.deployment import Deployment
1113

12-
from ocp_resources.model_registry import ModelRegistry
14+
from ocp_resources.model_registry_modelregistry_opendatahub_io import ModelRegistry
1315
from schemathesis.specs.openapi.schemas import BaseOpenAPISchema
1416
from schemathesis.generation.stateful.state_machine import APIStateMachine
1517
from schemathesis.core.transport import Response
@@ -29,6 +31,8 @@
2931
DB_RESOURCES_NAME,
3032
MODEL_REGISTRY_DB_SECRET_STR_DATA,
3133
MODEL_REGISTRY_DB_SECRET_ANNOTATIONS,
34+
OAUTH_PROXY_CONFIG_DICT,
35+
MODEL_REGISTRY_STANDARD_LABELS,
3236
)
3337
from utilities.constants import Labels
3438
from tests.model_registry.utils import (
@@ -38,7 +42,7 @@
3842
get_model_registry_db_label_dict,
3943
wait_for_pods_running,
4044
)
41-
from utilities.constants import Annotations, Protocols, DscComponents
45+
from utilities.constants import Protocols, DscComponents
4246
from model_registry import ModelRegistry as ModelRegistryClient
4347
from semver import Version
4448
from utilities.infra import get_product_version
@@ -145,66 +149,94 @@ def model_registry_db_deployment(
145149

146150
@pytest.fixture(scope="class")
147151
def model_registry_instance(
148-
admin_client: DynamicClient,
149-
model_registry_namespace: str,
150-
model_registry_db_deployment: Deployment,
151-
model_registry_db_secret: Secret,
152-
model_registry_db_service: Service,
152+
model_registry_namespace: str, model_registry_mysql_config: dict[str, Any], is_model_registry_oauth: bool
153153
) -> Generator[ModelRegistry, Any, Any]:
154+
istio_config = None
155+
oauth_config = None
156+
if is_model_registry_oauth:
157+
LOGGER.warning("Requested Ouath Proxy configuration:")
158+
oauth_config = OAUTH_PROXY_CONFIG_DICT
159+
else:
160+
LOGGER.warning("Requested OSSM configuration:")
161+
istio_config = ISTIO_CONFIG_DICT
162+
"""Creates a model registry instance with oauth proxy/service mesh configuration."""
154163
with ModelRegistry(
155164
name=MR_INSTANCE_NAME,
156165
namespace=model_registry_namespace,
157-
label={
158-
Annotations.KubernetesIo.NAME: MR_INSTANCE_NAME,
159-
Annotations.KubernetesIo.INSTANCE: MR_INSTANCE_NAME,
160-
Annotations.KubernetesIo.PART_OF: MR_OPERATOR_NAME,
161-
Annotations.KubernetesIo.CREATED_BY: MR_OPERATOR_NAME,
162-
},
166+
label=MODEL_REGISTRY_STANDARD_LABELS,
163167
grpc={},
164168
rest={},
165-
istio=ISTIO_CONFIG_DICT,
166-
mysql={
167-
"host": f"{model_registry_db_deployment.name}.{model_registry_db_deployment.namespace}.svc.cluster.local",
168-
"database": model_registry_db_secret.string_data["database-name"],
169-
"passwordSecret": {"key": "database-password", "name": DB_RESOURCES_NAME},
170-
"port": 3306,
171-
"skipDBCreation": False,
172-
"username": model_registry_db_secret.string_data["database-user"],
173-
},
169+
istio=istio_config,
170+
oauth_proxy=oauth_config,
171+
mysql=model_registry_mysql_config,
174172
wait_for_resource=True,
175173
) as mr:
176174
mr.wait_for_condition(condition="Available", status="True")
177175
yield mr
178176

179177

178+
@pytest.fixture(scope="class")
179+
def model_registry_mysql_config(
180+
model_registry_db_deployment: Deployment, model_registry_db_secret: Secret
181+
) -> dict[str, Any]:
182+
return {
183+
"host": f"{model_registry_db_deployment.name}.{model_registry_db_deployment.namespace}.svc.cluster.local",
184+
"database": model_registry_db_secret.string_data["database-name"],
185+
"passwordSecret": {"key": "database-password", "name": model_registry_db_deployment.name},
186+
"port": 3306,
187+
"skipDBCreation": False,
188+
"username": model_registry_db_secret.string_data["database-user"],
189+
}
190+
191+
180192
@pytest.fixture(scope="class")
181193
def model_registry_instance_service(
182194
admin_client: DynamicClient,
183195
model_registry_namespace: str,
184196
model_registry_instance: ModelRegistry,
185197
) -> Service:
198+
"""
199+
Get the service for the regular model registry instance.
200+
Args:
201+
admin_client: The admin client
202+
model_registry_namespace: The namespace where the model registry is deployed
203+
model_registry_instance: The model registry instance to get the service for
204+
Returns:
205+
Service: The service for the model registry instance
206+
"""
186207
return get_mr_service_by_label(
187-
client=admin_client, ns=Namespace(name=model_registry_namespace), mr_instance=model_registry_instance
208+
client=admin_client,
209+
ns=Namespace(name=model_registry_namespace),
210+
mr_instance=model_registry_instance,
188211
)
189212

190213

191214
@pytest.fixture(scope="class")
192215
def model_registry_instance_rest_endpoint(
193216
model_registry_instance_service: Service,
194217
) -> str:
218+
"""
219+
Get the REST endpoint for the model registry instance.
220+
Args:
221+
model_registry_instance_service: The service for the model registry instance
222+
Returns:
223+
str: The REST endpoint for the model registry instance
224+
"""
195225
return get_endpoint_from_mr_service(svc=model_registry_instance_service, protocol=Protocols.REST)
196226

197227

198228
@pytest.fixture(scope="class")
199-
def generated_schema(model_registry_instance_rest_endpoint: str) -> BaseOpenAPISchema:
229+
def generated_schema(pytestconfig: Config, model_registry_instance_rest_endpoint: str) -> BaseOpenAPISchema:
230+
os.environ["API_HOST"] = model_registry_instance_rest_endpoint
231+
config = schemathesis.config.SchemathesisConfig.from_path(f"{pytestconfig.rootpath}/schemathesis.toml")
200232
schema = schemathesis.openapi.from_url(
201-
url="https://raw.githubusercontent.com/kubeflow/model-registry/main/api/openapi/model-registry.yaml"
233+
url="https://raw.githubusercontent.com/kubeflow/model-registry/main/api/openapi/model-registry.yaml",
234+
config=config,
202235
)
203-
schema.configure(base_url=f"https://{model_registry_instance_rest_endpoint}/")
204236
return schema
205237

206238

207-
@pytest.fixture
239+
@pytest.fixture()
208240
def state_machine(generated_schema: BaseOpenAPISchema, current_client_token: str) -> APIStateMachine:
209241
BaseAPIWorkflow = generated_schema.as_state_machine()
210242

@@ -214,12 +246,18 @@ class APIWorkflow(BaseAPIWorkflow): # type: ignore
214246
def setup(self) -> None:
215247
self.headers = {"Authorization": f"Bearer {current_client_token}", "Content-Type": "application/json"}
216248

249+
def before_call(self, case: Case) -> None:
250+
LOGGER.info(f"Checking: {case.method} {case.path}")
251+
217252
# these kwargs are passed to requests.request()
218253
def get_call_kwargs(self, case: Case) -> dict[str, Any]:
219254
return {"verify": False, "headers": self.headers}
220255

221256
def after_call(self, response: Response, case: Case) -> None:
222-
LOGGER.info(f"{case.method} {case.path} -> {response.status_code}")
257+
LOGGER.info(
258+
f"Method tested: {case.method}, API: {case.path}, response code:{response.status_code},"
259+
f" Full Response:{response.text}"
260+
)
223261

224262
return APIWorkflow
225263

@@ -263,11 +301,18 @@ def updated_dsc_component_state_scope_class(
263301

264302
@pytest.fixture(scope="class")
265303
def model_registry_client(current_client_token: str, model_registry_instance_rest_endpoint: str) -> ModelRegistryClient:
266-
# address and port need to be split in the client instantiation
304+
"""
305+
Get a client for the model registry instance.
306+
Args:
307+
request: The pytest request object
308+
current_client_token: The current client token
309+
Returns:
310+
ModelRegistryClient: A client for the model registry instance
311+
"""
267312
server, port = model_registry_instance_rest_endpoint.split(":")
268313
return ModelRegistryClient(
269314
server_address=f"{Protocols.HTTPS}://{server}",
270-
port=port,
315+
port=int(port),
271316
author="opendatahub-test",
272317
user_token=current_client_token,
273318
is_secure=False,
@@ -350,3 +395,8 @@ def validate_authorino_operator_version_channel(admin_client: DynamicClient) ->
350395
validate_operator_subscription_channel(
351396
client=admin_client, namespace="openshift-operators", operator_name=operator_name, channel_name="stable"
352397
)
398+
399+
400+
@pytest.fixture(scope="class")
401+
def is_model_registry_oauth(request: FixtureRequest) -> bool:
402+
return getattr(request, "param", {}).get("use_oauth_proxy", False)

tests/model_registry/constants.py

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from typing import Any
22
from ocp_resources.resource import Resource
3-
from utilities.constants import ModelFormat
3+
from utilities.constants import ModelFormat, Annotations
44

55

66
class ModelRegistryEndpoints:
@@ -29,6 +29,11 @@ class ModelRegistryEndpoints:
2929
ISTIO_CONFIG_DICT: dict[str, Any] = {
3030
"gateway": {"grpc": {"tls": {}}, "rest": {"tls": {}}},
3131
}
32+
OAUTH_PROXY_CONFIG_DICT: dict[str, Any] = {
33+
"port": 8443,
34+
"routePort": 443,
35+
"serviceRoute": "enabled",
36+
}
3237
DB_RESOURCES_NAME: str = "db-model-registry"
3338
MR_DB_IMAGE_DIGEST: str = (
3439
"public.ecr.aws/docker/library/mysql@sha256:9de9d54fecee6253130e65154b930978b1fcc336bcc86dfd06e89b72a2588ebe"
@@ -43,3 +48,9 @@ class ModelRegistryEndpoints:
4348
f"{Resource.ApiGroup.TEMPLATE_OPENSHIFT_IO}/expose-password": "'{.data[''database-password'']}'",
4449
f"{Resource.ApiGroup.TEMPLATE_OPENSHIFT_IO}/expose-username": "'{.data[''database-user'']}'",
4550
}
51+
MODEL_REGISTRY_STANDARD_LABELS = {
52+
Annotations.KubernetesIo.NAME: MR_INSTANCE_NAME,
53+
Annotations.KubernetesIo.INSTANCE: MR_INSTANCE_NAME,
54+
Annotations.KubernetesIo.PART_OF: MR_OPERATOR_NAME,
55+
Annotations.KubernetesIo.CREATED_BY: MR_OPERATOR_NAME,
56+
}

tests/model_registry/image_validation/test_verify_rhoai_images.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
from utilities.general import (
99
validate_container_images,
1010
)
11-
from ocp_resources.model_registry import ModelRegistry
11+
from ocp_resources.model_registry_modelregistry_opendatahub_io import ModelRegistry
1212
from ocp_resources.pod import Pod
1313

1414
LOGGER = get_logger(name=__name__)
@@ -29,6 +29,7 @@
2929
indirect=True,
3030
)
3131
@pytest.mark.usefixtures("updated_dsc_component_state_scope_class")
32+
@pytest.mark.downstream_only
3233
class TestModelRegistryImages:
3334
"""
3435
Tests to verify that all Model Registry component images (operator and instance container images)

0 commit comments

Comments
 (0)