Skip to content

Commit 2d5e082

Browse files
authored
Merge branch 'main' into hf_negative
2 parents 09cf931 + f206021 commit 2d5e082

File tree

13 files changed

+224
-24
lines changed

13 files changed

+224
-24
lines changed

tests/model_registry/model_catalog/conftest.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ def enabled_model_catalog_config_map(
4444
admin_client: DynamicClient,
4545
model_registry_namespace: str,
4646
current_client_token: str,
47-
) -> ConfigMap:
47+
) -> Generator[ConfigMap, None, None]:
4848
"""
4949
Enable all catalogs in the default model catalog configmap
5050
"""
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import pytest
2+
import yaml
3+
from typing import Generator
4+
5+
from kubernetes.dynamic import DynamicClient
6+
from ocp_resources.config_map import ConfigMap
7+
from ocp_resources.resource import ResourceEditor
8+
9+
from tests.model_registry.constants import DEFAULT_CUSTOM_MODEL_CATALOG
10+
from tests.model_registry.utils import is_model_catalog_ready, wait_for_model_catalog_api
11+
12+
13+
@pytest.fixture()
14+
def disabled_catalog_source(
15+
request: pytest.FixtureRequest,
16+
admin_client: DynamicClient,
17+
model_registry_namespace: str,
18+
model_catalog_rest_url: list[str],
19+
model_registry_rest_headers: dict[str, str],
20+
) -> Generator[str, None, None]:
21+
"""
22+
Disables an existing catalog source for testing.
23+
24+
Yields:
25+
str: The catalog ID of the disabled catalog.
26+
"""
27+
sources_cm = ConfigMap(name=DEFAULT_CUSTOM_MODEL_CATALOG, client=admin_client, namespace=model_registry_namespace)
28+
current_data = yaml.safe_load(sources_cm.instance.data["sources.yaml"])
29+
30+
# Get catalog_id from parameter (required)
31+
catalog_id = request.param
32+
assert catalog_id, "catalog_id parameter is required for disabled_catalog_source fixture"
33+
34+
# Find catalog by ID
35+
catalog_to_disable = next(
36+
(catalog for catalog in current_data.get("catalogs", []) if catalog["id"] == catalog_id), None
37+
)
38+
assert catalog_to_disable is not None, f"Catalog '{catalog_id}' not found in ConfigMap"
39+
40+
# Disable the catalog
41+
catalog_to_disable["enabled"] = False
42+
43+
patches = {"data": {"sources.yaml": yaml.dump(current_data, default_flow_style=False)}}
44+
45+
with ResourceEditor(patches={sources_cm: patches}):
46+
is_model_catalog_ready(client=admin_client, model_registry_namespace=model_registry_namespace)
47+
wait_for_model_catalog_api(url=model_catalog_rest_url[0], headers=model_registry_rest_headers)
48+
49+
yield catalog_id
50+
51+
is_model_catalog_ready(client=admin_client, model_registry_namespace=model_registry_namespace)
52+
wait_for_model_catalog_api(url=model_catalog_rest_url[0], headers=model_registry_rest_headers)
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
import pytest
2+
3+
from ocp_resources.config_map import ConfigMap
4+
from simple_logger.logger import get_logger
5+
6+
from tests.model_registry.utils import execute_get_command
7+
from tests.model_registry.model_catalog.metadata.utils import validate_source_status
8+
9+
pytestmark = [pytest.mark.usefixtures("updated_dsc_component_state_scope_session", "model_registry_namespace")]
10+
11+
LOGGER = get_logger(name=__name__)
12+
13+
14+
class TestSourcesEndpoint:
15+
"""Test class for the model catalog sources endpoint."""
16+
17+
@pytest.mark.smoke
18+
def test_available_source_status(
19+
self,
20+
enabled_model_catalog_config_map: ConfigMap,
21+
model_catalog_rest_url: list[str],
22+
model_registry_rest_headers: dict[str, str],
23+
):
24+
"""
25+
RHOAIENG-41849: Test that the sources endpoint returns no error for available sources.
26+
"""
27+
response = execute_get_command(url=f"{model_catalog_rest_url[0]}sources", headers=model_registry_rest_headers)
28+
items = response.get("items", [])
29+
assert items, "Sources not found"
30+
for item in items:
31+
validate_source_status(catalog=item, expected_status="available")
32+
error_value = item["error"]
33+
assert error_value is None or error_value == "", (
34+
f"Source '{item.get('id')}' should not have error, got: {error_value}"
35+
)
36+
37+
LOGGER.info(
38+
f"Available catalog verified - ID: {item.get('id')}, Status: {item.get('status')}, Error: {error_value}"
39+
)
40+
41+
@pytest.mark.parametrize("disabled_catalog_source", ["redhat_ai_models"], indirect=True)
42+
def test_disabled_source_status(
43+
self,
44+
enabled_model_catalog_config_map: ConfigMap,
45+
disabled_catalog_source: str,
46+
model_catalog_rest_url: list[str],
47+
model_registry_rest_headers: dict[str, str],
48+
):
49+
"""
50+
RHOAIENG-41849:
51+
This test disables an existing catalog and verifies:
52+
- status field is "disabled"
53+
- error field is null or empty
54+
"""
55+
catalog_id = disabled_catalog_source
56+
57+
response = execute_get_command(url=f"{model_catalog_rest_url[0]}sources", headers=model_registry_rest_headers)
58+
items = response.get("items", [])
59+
60+
# Find the disabled catalog
61+
disabled_catalog = next((item for item in items if item.get("id") == catalog_id), None)
62+
assert disabled_catalog is not None, f"Disabled catalog '{catalog_id}' not found in sources"
63+
64+
# Validate status and error fields
65+
validate_source_status(catalog=disabled_catalog, expected_status="disabled")
66+
error_value = disabled_catalog["error"]
67+
assert error_value is None or error_value == "", (
68+
f"Source '{disabled_catalog.get('id')}' should not have error, got: {error_value}"
69+
)
70+
71+
LOGGER.info(
72+
"Disabled catalog verified - "
73+
f"ID: {disabled_catalog.get('id')}, "
74+
f"Status: {disabled_catalog.get('status')}, "
75+
f"Error: {error_value}"
76+
)
77+
78+
@pytest.mark.parametrize("disabled_catalog_source", ["redhat_ai_models"], indirect=True)
79+
@pytest.mark.sanity
80+
def test_sources_endpoint_returns_all_sources_regardless_of_enabled_field(
81+
self,
82+
enabled_model_catalog_config_map: ConfigMap,
83+
disabled_catalog_source: str,
84+
model_catalog_rest_url: list[str],
85+
model_registry_rest_headers: dict[str, str],
86+
):
87+
"""
88+
RHOAIENG-41633: Test that sources endpoint returns ALL sources regardless of enabled field value.
89+
"""
90+
response = execute_get_command(url=f"{model_catalog_rest_url[0]}sources", headers=model_registry_rest_headers)
91+
items = response.get("items", [])
92+
93+
assert len(items) > 1, "Expected multiple sources to be returned"
94+
95+
# Verify we have at least one enabled source
96+
enabled_sources = [item for item in items if item.get("status") == "available"]
97+
assert enabled_sources, "Expected at least one enabled source"
98+
99+
# Verify we have at least one disabled source
100+
disabled_sources = [item for item in items if item.get("status") == "disabled"]
101+
assert disabled_sources, "Expected at least one disabled source"
102+
103+
assert len(enabled_sources) + len(disabled_sources) == len(items), "Expected all sources to be returned"
104+
105+
LOGGER.info(
106+
f"Sources endpoint returned {len(items)} total sources: "
107+
f"{len(enabled_sources)} enabled, {len(disabled_sources)} disabled"
108+
)

tests/model_registry/model_catalog/metadata/utils.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -653,3 +653,19 @@ def verify_labels_match(expected_labels: List[Dict[str, Any]], api_labels: List[
653653
break
654654

655655
assert found, f"Expected label not found in API response: {expected_label}"
656+
657+
658+
def validate_source_status(catalog: dict[str, Any], expected_status: str) -> None:
659+
"""
660+
Validate the status field of a catalog source.
661+
662+
Args:
663+
catalog: The catalog source dictionary from API response
664+
expected_status: The expected status value (e.g., "available", "disabled", "error")
665+
666+
Raises:
667+
AssertionError: If status field does not match expected value
668+
"""
669+
assert catalog.get("status") == expected_status, (
670+
f"Source '{catalog.get('id')}' status should be '{expected_status}', got: {catalog.get('status')}"
671+
)

tests/model_registry/model_registry/negative_tests/conftest.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,7 @@ def model_registry_db_deployment_negative_test(
119119
model_registry_db_service_for_negative_tests: Service,
120120
) -> Generator[Deployment, Any, Any]:
121121
with Deployment(
122+
client=admin_client,
122123
name=DB_RESOURCES_NAME_NEGATIVE,
123124
namespace=model_registry_namespace_for_negative_tests.name,
124125
annotations={
@@ -168,9 +169,9 @@ def model_registry_db_instance_pod(admin_client: DynamicClient) -> Generator[Pod
168169

169170

170171
@pytest.fixture()
171-
def delete_mr_deployment() -> None:
172+
def delete_mr_deployment(admin_client: DynamicClient) -> None:
172173
"""Delete the model registry deployment"""
173174
mr_deployment = Deployment(
174-
name=MR_INSTANCE_NAME, namespace=py_config["model_registry_namespace"], ensure_exists=True
175+
client=admin_client, name=MR_INSTANCE_NAME, namespace=py_config["model_registry_namespace"], ensure_exists=True
175176
)
176177
mr_deployment.delete(wait=True)

tests/model_registry/model_registry/negative_tests/test_model_registry_creation_negative.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
DB_RESOURCE_NAME,
1515
)
1616
from kubernetes.dynamic.exceptions import ForbiddenError
17+
from kubernetes.dynamic import DynamicClient
1718

1819

1920
LOGGER = get_logger(name=__name__)
@@ -30,6 +31,7 @@ class TestModelRegistryCreationNegative:
3031
@pytest.mark.sanity
3132
def test_registering_model_negative(
3233
self: Self,
34+
admin_client: DynamicClient,
3335
current_client_token: str,
3436
model_registry_namespace_for_negative_tests: Namespace,
3537
updated_dsc_component_state_scope_session: DataScienceCluster,
@@ -50,6 +52,7 @@ def test_registering_model_negative(
5052
match=f"namespace must be {py_config['model_registry_namespace']}",
5153
):
5254
with ModelRegistry(
55+
client=admin_client,
5356
name=MR_INSTANCE_NAME,
5457
namespace=model_registry_namespace_for_negative_tests.name,
5558
label={

tests/model_registry/model_registry/rbac/conftest.py

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -139,13 +139,16 @@ def created_role_binding_user(
139139
# RESOURCE FIXTURES PARMETRIZED
140140
# =============================================================================
141141
@pytest.fixture(scope="class")
142-
def db_secret_parametrized(request: FixtureRequest, teardown_resources: bool) -> Generator[List[Secret], Any, Any]:
142+
def db_secret_parametrized(
143+
request: FixtureRequest, admin_client: DynamicClient, teardown_resources: bool
144+
) -> Generator[List[Secret], Any, Any]:
143145
"""Create DB Secret parametrized"""
144146
with ExitStack() as stack:
145147
secrets = [
146148
stack.enter_context(
147149
Secret(
148150
**param,
151+
client=admin_client,
149152
teardown=teardown_resources,
150153
)
151154
)
@@ -156,14 +159,15 @@ def db_secret_parametrized(request: FixtureRequest, teardown_resources: bool) ->
156159

157160
@pytest.fixture(scope="class")
158161
def db_pvc_parametrized(
159-
request: FixtureRequest, teardown_resources: bool
162+
request: FixtureRequest, admin_client: DynamicClient, teardown_resources: bool
160163
) -> Generator[List[PersistentVolumeClaim], Any, Any]:
161164
"""Create DB PVC parametrized"""
162165
with ExitStack() as stack:
163166
pvc = [
164167
stack.enter_context(
165168
PersistentVolumeClaim(
166169
**param,
170+
client=admin_client,
167171
teardown=teardown_resources,
168172
)
169173
)
@@ -173,13 +177,16 @@ def db_pvc_parametrized(
173177

174178

175179
@pytest.fixture(scope="class")
176-
def db_service_parametrized(request: FixtureRequest, teardown_resources: bool) -> Generator[List[Service], Any, Any]:
180+
def db_service_parametrized(
181+
request: FixtureRequest, admin_client: DynamicClient, teardown_resources: bool
182+
) -> Generator[List[Service], Any, Any]:
177183
"""Create DB Service parametrized"""
178184
with ExitStack() as stack:
179185
services = [
180186
stack.enter_context(
181187
Service(
182188
**param,
189+
client=admin_client,
183190
teardown=teardown_resources,
184191
)
185192
)
@@ -190,14 +197,15 @@ def db_service_parametrized(request: FixtureRequest, teardown_resources: bool) -
190197

191198
@pytest.fixture(scope="class")
192199
def db_deployment_parametrized(
193-
request: FixtureRequest, teardown_resources: bool
200+
request: FixtureRequest, admin_client: DynamicClient, teardown_resources: bool
194201
) -> Generator[List[Deployment], Any, Any]:
195202
"""Create DB Deployment parametrized"""
196203
with ExitStack() as stack:
197204
deployments = [
198205
stack.enter_context(
199206
Deployment(
200207
**param,
208+
client=admin_client,
201209
teardown=teardown_resources,
202210
)
203211
)
@@ -217,7 +225,7 @@ def model_registry_instance_parametrized(
217225
"""Create Model Registry instance parametrized"""
218226
with ExitStack() as stack:
219227
model_registry_instances = []
220-
mr_instances = [stack.enter_context(ModelRegistry(**param)) for param in request.param]
228+
mr_instances = [stack.enter_context(ModelRegistry(**param, client=admin_client)) for param in request.param]
221229
for mr_instance in mr_instances:
222230
# Common parameters for both ModelRegistry classes
223231
mr_instance.wait_for_condition(condition="Available", status="True")

tests/model_registry/model_registry/rest_api/conftest.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,7 @@ def deploy_secure_mysql_and_mr(
155155
if "sslRootCertificateConfigMap" in param:
156156
mysql["sslRootCertificateConfigMap"] = param["sslRootCertificateConfigMap"]
157157
with ModelRegistry(
158+
client=admin_client,
158159
name=SECURE_MR_NAME,
159160
namespace=model_registry_namespace,
160161
label=get_mr_standard_labels(resource_name=SECURE_MR_NAME),
@@ -361,12 +362,15 @@ def skip_if_not_default_db(request):
361362

362363
@pytest.fixture()
363364
def model_registry_default_postgres_deployment_match_label(
364-
model_registry_namespace: str, model_registry_instance: list[ModelRegistry]
365+
model_registry_namespace: str, admin_client: DynamicClient, model_registry_instance: list[ModelRegistry]
365366
) -> dict[str, str]:
366367
"""
367368
Returns the matchLabels from the default postgres deployment for filtering pods.
368369
"""
369370
deployment = Deployment(
370-
namespace=model_registry_namespace, name=f"{model_registry_instance[0].name}-postgres", ensure_exists=True
371+
client=admin_client,
372+
namespace=model_registry_namespace,
373+
name=f"{model_registry_instance[0].name}-postgres",
374+
ensure_exists=True,
371375
)
372376
return deployment.instance.spec.selector.matchLabels

tests/model_registry/model_registry/rest_api/test_model_registry_rest_api.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -168,9 +168,11 @@ def test_default_postgres_db_pod_log(
168168

169169
def test_model_registry_validate_api_version(
170170
self: Self,
171-
model_registry_instance,
171+
admin_client: DynamicClient,
172+
model_registry_instance: list[ModelRegistry],
172173
):
173174
api_version = ModelRegistry(
175+
client=admin_client,
174176
name=model_registry_instance[0].name,
175177
namespace=model_registry_instance[0].namespace,
176178
ensure_exists=True,
@@ -181,7 +183,7 @@ def test_model_registry_validate_api_version(
181183

182184
def test_model_registry_validate_kuberbacproxy_enabled(
183185
self: Self,
184-
model_registry_instance,
186+
model_registry_instance: list[ModelRegistry],
185187
):
186188
model_registry_instance_spec = model_registry_instance[0].instance.spec
187189
LOGGER.info(f"Validating that MR is using kubeRBAC proxy {model_registry_instance_spec}")

tests/model_registry/model_registry/rest_api/test_multiple_mr.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,11 +42,17 @@
4242
class TestModelRegistryMultipleInstances:
4343
@pytest.mark.smoke
4444
def test_validate_multiple_model_registry(
45-
self: Self, model_registry_instance: list[ModelRegistry], model_registry_namespace: str
45+
self: Self,
46+
admin_client: DynamicClient,
47+
model_registry_instance: list[ModelRegistry],
48+
model_registry_namespace: str,
4649
):
4750
for num in range(0, NUM_RESOURCES["num_resources"]):
4851
mr = ModelRegistry(
49-
name=f"{MR_INSTANCE_BASE_NAME}{num}", namespace=model_registry_namespace, ensure_exists=True
52+
client=admin_client,
53+
name=f"{MR_INSTANCE_BASE_NAME}{num}",
54+
namespace=model_registry_namespace,
55+
ensure_exists=True,
5056
)
5157
LOGGER.info(f"{mr.name} found")
5258

0 commit comments

Comments
 (0)