Skip to content

Commit 48945da

Browse files
committed
Merge remote-tracking branch 'upstream/main' into cp_maria
2 parents eed40ec + 76ce3db commit 48945da

26 files changed

+689
-681
lines changed

.pre-commit-config.yaml

Lines changed: 1 addition & 1 deletion
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.12.1
38+
rev: v0.12.2
3939
hooks:
4040
- id: ruff
4141
- id: ruff-format

pyproject.toml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,6 @@ dependencies = [
5050
"pyyaml",
5151
"tenacity",
5252
"types-requests>=2.32.0.20241016",
53-
"schemathesis>=4.0.0a8",
5453
"requests",
5554
"pytest-asyncio",
5655
"syrupy",

schemathesis.toml

Lines changed: 0 additions & 1 deletion
This file was deleted.

tests/model_explainability/lm_eval/utils.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
LOGGER = get_logger(name=__name__)
1010

1111

12-
def get_lmevaljob_pod(client: DynamicClient, lmevaljob: LMEvalJob, timeout: int = Timeout.TIMEOUT_2MIN) -> Pod:
12+
def get_lmevaljob_pod(client: DynamicClient, lmevaljob: LMEvalJob, timeout: int = Timeout.TIMEOUT_10MIN) -> Pod:
1313
"""
1414
Gets the pod corresponding to a given LMEvalJob and waits for it to be ready.
1515

tests/model_explainability/trustyai_service/service/test_trustyai_service.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -197,7 +197,6 @@ def test_validate_trustyai_service_image(
197197
indirect=True,
198198
)
199199
@pytest.mark.usefixtures("minio_pod")
200-
@pytest.mark.smoke
201200
def test_trustyai_service_db_migration(
202201
admin_client,
203202
current_client_token,

tests/model_explainability/trustyai_service/utils.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ def wait_for_mariadb_operator_deployments(mariadb_operator: MariadbOperator) ->
3838
deployment.wait_for_replicas()
3939

4040

41-
def wait_for_mariadb_pods(client: DynamicClient, mariadb: MariaDB, timeout: int = Timeout.TIMEOUT_5MIN) -> None:
41+
def wait_for_mariadb_pods(client: DynamicClient, mariadb: MariaDB, timeout: int = Timeout.TIMEOUT_15MIN) -> None:
4242
def _get_mariadb_pods() -> list[Pod]:
4343
_pods = [
4444
_pod

tests/model_registry/conftest.py

Lines changed: 67 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
1-
import os
21
import pytest
32
from pytest import Config
4-
import schemathesis
53
from typing import Generator, Any
64

75
from ocp_resources.infrastructure import Infrastructure
@@ -14,10 +12,6 @@
1412
from ocp_resources.deployment import Deployment
1513

1614
from ocp_resources.model_registry_modelregistry_opendatahub_io import ModelRegistry
17-
from schemathesis.specs.openapi.schemas import BaseOpenAPISchema
18-
from schemathesis.generation.stateful.state_machine import APIStateMachine
19-
from schemathesis.core.transport import Response
20-
from schemathesis.generation.case import Case
2115
from ocp_resources.resource import ResourceEditor
2216

2317
from pytest import FixtureRequest
@@ -37,15 +31,15 @@
3731
ISTIO_CONFIG_DICT,
3832
)
3933
from tests.model_registry.rest_api.utils import ModelRegistryV1Alpha1
40-
from utilities.constants import Labels
34+
from utilities.constants import Labels, Protocols
4135
from tests.model_registry.utils import (
4236
get_endpoint_from_mr_service,
4337
get_mr_service_by_label,
4438
get_model_registry_deployment_template_dict,
4539
get_model_registry_db_label_dict,
4640
wait_for_pods_running,
4741
)
48-
from utilities.constants import Protocols, DscComponents
42+
from utilities.constants import DscComponents
4943
from model_registry import ModelRegistry as ModelRegistryClient
5044
from semver import Version
5145
from utilities.general import wait_for_pods_by_labels
@@ -235,7 +229,7 @@ def model_registry_instance_mysql(
235229
mr.wait_for_condition(condition="Available", status="True")
236230
mr.wait_for_condition(condition="OAuthProxyAvailable", status="True")
237231
wait_for_pods_running(
238-
admin_client=admin_client, namespace_name=model_registry_namespace, number_of_consecutive_checks=3
232+
admin_client=admin_client, namespace_name=model_registry_namespace, number_of_consecutive_checks=6
239233
)
240234
yield mr
241235

@@ -279,43 +273,6 @@ def model_registry_instance_rest_endpoint(admin_client: DynamicClient, model_reg
279273
)
280274

281275

282-
@pytest.fixture(scope="class")
283-
def generated_schema(pytestconfig: Config, model_registry_instance_rest_endpoint: str) -> BaseOpenAPISchema:
284-
os.environ["API_HOST"] = model_registry_instance_rest_endpoint
285-
config = schemathesis.config.SchemathesisConfig.from_path(f"{pytestconfig.rootpath}/schemathesis.toml")
286-
schema = schemathesis.openapi.from_url(
287-
url="https://raw.githubusercontent.com/kubeflow/model-registry/main/api/openapi/model-registry.yaml",
288-
config=config,
289-
)
290-
return schema
291-
292-
293-
@pytest.fixture()
294-
def state_machine(generated_schema: BaseOpenAPISchema, current_client_token: str) -> APIStateMachine:
295-
BaseAPIWorkflow = generated_schema.as_state_machine()
296-
297-
class APIWorkflow(BaseAPIWorkflow): # type: ignore
298-
headers: dict[str, str]
299-
300-
def setup(self) -> None:
301-
self.headers = {"Authorization": f"Bearer {current_client_token}", "Content-Type": "application/json"}
302-
303-
def before_call(self, case: Case) -> None:
304-
LOGGER.info(f"Checking: {case.method} {case.path}")
305-
306-
# these kwargs are passed to requests.request()
307-
def get_call_kwargs(self, case: Case) -> dict[str, Any]:
308-
return {"verify": False, "headers": self.headers}
309-
310-
def after_call(self, response: Response, case: Case) -> None:
311-
LOGGER.info(
312-
f"Method tested: {case.method}, API: {case.path}, response code:{response.status_code},"
313-
f" Full Response:{response.text}"
314-
)
315-
316-
return APIWorkflow
317-
318-
319276
@pytest.fixture(scope="class")
320277
def updated_dsc_component_state_scope_class(
321278
pytestconfig: Config,
@@ -481,6 +438,55 @@ def model_registry_instance_pod(admin_client: DynamicClient) -> Generator[Pod, A
481438
)[0]
482439

483440

441+
@pytest.fixture()
442+
def model_registry_db_instance_pod(admin_client: DynamicClient) -> Generator[Pod, Any, Any]:
443+
"""Get the model registry instance pod."""
444+
yield wait_for_pods_by_labels(
445+
admin_client=admin_client,
446+
namespace=py_config["model_registry_namespace"],
447+
label_selector=f"name={DB_RESOURCES_NAME}",
448+
expected_num_pods=1,
449+
)[0]
450+
451+
452+
@pytest.fixture()
453+
def set_mr_db_dirty(model_registry_db_instance_pod: Pod) -> int:
454+
"""Set the model registry database dirty and return the latest migration version"""
455+
output = model_registry_db_instance_pod.execute(
456+
command=[
457+
"mysql",
458+
"-u",
459+
MODEL_REGISTRY_DB_SECRET_STR_DATA["database-user"],
460+
f"-p{MODEL_REGISTRY_DB_SECRET_STR_DATA['database-password']}",
461+
"-e",
462+
"SELECT version FROM schema_migrations ORDER BY version DESC LIMIT 1;",
463+
MODEL_REGISTRY_DB_SECRET_STR_DATA["database-name"],
464+
]
465+
)
466+
latest_migration_version = int(output.strip().split()[1])
467+
model_registry_db_instance_pod.execute(
468+
command=[
469+
"mysql",
470+
"-u",
471+
MODEL_REGISTRY_DB_SECRET_STR_DATA["database-user"],
472+
f"-p{MODEL_REGISTRY_DB_SECRET_STR_DATA['database-password']}",
473+
"-e",
474+
f"UPDATE schema_migrations SET dirty = 1 WHERE version = {latest_migration_version};",
475+
MODEL_REGISTRY_DB_SECRET_STR_DATA["database-name"],
476+
]
477+
)
478+
return latest_migration_version
479+
480+
481+
@pytest.fixture()
482+
def delete_mr_deployment() -> None:
483+
"""Delete the model registry deployment"""
484+
mr_deployment = Deployment(
485+
name=MR_INSTANCE_NAME, namespace=py_config["model_registry_namespace"], ensure_exists=True
486+
)
487+
mr_deployment.delete(wait=True)
488+
489+
484490
@pytest.fixture(scope="class")
485491
def is_model_registry_oauth(request: FixtureRequest) -> bool:
486492
return getattr(request, "param", {}).get("use_oauth_proxy", True)
@@ -490,3 +496,18 @@ def is_model_registry_oauth(request: FixtureRequest) -> bool:
490496
def api_server_url(admin_client: DynamicClient) -> str:
491497
infrastructure = Infrastructure(client=admin_client, name="cluster", ensure_exists=True)
492498
return infrastructure.instance.status.apiServerURL
499+
500+
501+
@pytest.fixture(scope="class")
502+
def model_registry_rest_url(model_registry_instance_rest_endpoint: str) -> str:
503+
# address and port need to be split in the client instantiation
504+
return f"{Protocols.HTTPS}://{model_registry_instance_rest_endpoint}"
505+
506+
507+
@pytest.fixture(scope="class")
508+
def model_registry_rest_headers(current_client_token: str) -> dict[str, str]:
509+
return {
510+
"Authorization": f"Bearer {current_client_token}",
511+
"accept": "application/json",
512+
"Content-Type": "application/json",
513+
}
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
import pytest
2+
from typing import Self
3+
from simple_logger.logger import get_logger
4+
from pytest_testconfig import config as py_config
5+
6+
from ocp_resources.pod import Pod
7+
from utilities.constants import DscComponents
8+
from tests.model_registry.constants import MR_INSTANCE_NAME
9+
from kubernetes.dynamic.client import DynamicClient
10+
from utilities.general import wait_for_pods_by_labels
11+
12+
13+
LOGGER = get_logger(name=__name__)
14+
15+
16+
@pytest.mark.parametrize(
17+
"updated_dsc_component_state_scope_class",
18+
[
19+
pytest.param({
20+
"component_patch": {
21+
DscComponents.MODELREGISTRY: {
22+
"managementState": DscComponents.ManagementState.MANAGED,
23+
"registriesNamespace": py_config["model_registry_namespace"],
24+
},
25+
}
26+
}),
27+
],
28+
indirect=True,
29+
)
30+
@pytest.mark.usefixtures(
31+
"updated_dsc_component_state_scope_class", "model_registry_mysql_metadata_db", "model_registry_instance_mysql"
32+
)
33+
class TestDBMigration:
34+
def test_db_migration_negative(
35+
self: Self,
36+
admin_client: DynamicClient,
37+
model_registry_db_instance_pod: Pod,
38+
set_mr_db_dirty: int,
39+
delete_mr_deployment: None,
40+
):
41+
"""
42+
RHOAIENG-27505: This test is to check the migration error when the database is dirty.
43+
The test will:
44+
1. Set the dirty flag to 1 for the latest migration version
45+
2. Delete the model registry deployment
46+
3. Check the logs for the expected error
47+
"""
48+
mr_pods = wait_for_pods_by_labels(
49+
admin_client=admin_client,
50+
namespace=py_config["model_registry_namespace"],
51+
label_selector=f"app={MR_INSTANCE_NAME}",
52+
expected_num_pods=1,
53+
)
54+
mr_pod = mr_pods[0]
55+
LOGGER.info("Checking the logs for the expected error")
56+
57+
log_output = mr_pod.log(container="rest-container")
58+
expected_error = (
59+
f"Error: {{{{ALERT}}}} error connecting to datastore: Dirty database version {set_mr_db_dirty}. "
60+
"Fix and force version."
61+
)
62+
assert expected_error in log_output, "Expected error message not found in logs!"
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
from typing import Any
2+
3+
import pytest
4+
5+
from ocp_resources.deployment import Deployment
6+
from tests.model_registry.constants import MR_INSTANCE_NAME
7+
8+
9+
@pytest.fixture(scope="class")
10+
def model_registry_deployment_containers(model_registry_namespace: str) -> dict[str, Any]:
11+
return Deployment(
12+
name=MR_INSTANCE_NAME, namespace=model_registry_namespace, ensure_exists=True
13+
).instance.spec.template.spec.containers

tests/model_registry/python_client/test_model_registry_creation.py

Lines changed: 40 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
import pytest
2-
from typing import Self
2+
from typing import Self, Any
33
from simple_logger.logger import get_logger
44
from pytest_testconfig import config as py_config
55

66
from ocp_resources.pod import Pod
7+
from tests.model_registry.utils import execute_model_registry_get_command
78
from utilities.constants import DscComponents
89
from tests.model_registry.constants import MODEL_NAME, MODEL_DICT
910
from model_registry import ModelRegistry as ModelRegistryClient
@@ -89,7 +90,41 @@ def test_model_registry_operator_env(
8990
if not namespace_env:
9091
pytest.fail("Missing environment variable REGISTRIES_NAMESPACE")
9192

92-
# TODO: Edit a registered model
93-
# TODO: Add additional versions for a model
94-
# TODO: List all available models
95-
# TODO: List all versions of a model
93+
def test_model_registry_grpc_container_removal(self, model_registry_deployment_containers: dict[str, Any]):
94+
"""
95+
RHOAIENG-26239: Test to ensure removal of grpc container from model registry deployment
96+
Steps:
97+
Create metadata database
98+
Deploys model registry using the same
99+
Check model registry deployment for grpc container. It should not be present
100+
"""
101+
102+
for container in model_registry_deployment_containers:
103+
if "grpc" in container["name"]:
104+
pytest.fail(f"GRPC container found: {container}")
105+
106+
@pytest.mark.parametrize(
107+
"endpoint",
108+
[
109+
pytest.param(
110+
"oauth/healthz",
111+
),
112+
pytest.param(
113+
"readyz/isDirty",
114+
),
115+
],
116+
)
117+
def test_model_registry_endpoint_response(
118+
self, model_registry_rest_url: str, model_registry_rest_headers: dict[str, str], endpoint: str
119+
):
120+
"""
121+
RHOAIENG-26239: Test to ensure model registry endpoints are responsive
122+
Steps:
123+
Create metadata database
124+
Deploys model registry using the same
125+
Ensure endpoint is responsive via get call
126+
"""
127+
output = execute_model_registry_get_command(
128+
url=f"{model_registry_rest_url}/{endpoint}", headers=model_registry_rest_headers, json_output=False
129+
)
130+
assert output["raw_output"].lower() == "OK".lower()

0 commit comments

Comments
 (0)