Skip to content

Commit 00797e8

Browse files
authored
Merge branch 'main' into guardrails-healthcheck
2 parents 64ff51b + c4fef85 commit 00797e8

File tree

67 files changed

+881
-1331
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

67 files changed

+881
-1331
lines changed

.pre-commit-config.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ repos:
3636
exclude: .*/__snapshots__/.*|.*-input\.json$
3737

3838
- repo: https://github.com/astral-sh/ruff-pre-commit
39-
rev: v0.14.10
39+
rev: v0.14.11
4040
hooks:
4141
- id: ruff
4242
- id: ruff-format

OWNERS

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
11
approvers:
2-
- <>
2+
- dbasunag
3+
- jgarciao
4+
- lugi0
35
reviewers:
4-
- <>
6+
- dbasunag
7+
- jgarciao
8+
- lugi0
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
import pytest
2+
from simple_logger.logger import get_logger
3+
4+
from tests.model_registry.model_catalog.constants import REDHAT_AI_CATALOG_ID
5+
from tests.model_registry.utils import execute_get_command
6+
7+
LOGGER = get_logger(name=__name__)
8+
9+
pytestmark = [pytest.mark.usefixtures("updated_dsc_component_state_scope_session", "model_registry_namespace")]
10+
11+
12+
class TestCatalogSourceMerge:
13+
"""
14+
Test catalog source merging behavior when the same source ID appears in both
15+
default and custom ConfigMaps.
16+
"""
17+
18+
@pytest.mark.parametrize(
19+
"sparse_override_catalog_source",
20+
[
21+
{"id": REDHAT_AI_CATALOG_ID, "field_name": "name", "field_value": "Custom Override Name"},
22+
{"id": REDHAT_AI_CATALOG_ID, "field_name": "labels", "field_value": ["custom-label", "override-label"]},
23+
{"id": REDHAT_AI_CATALOG_ID, "field_name": "enabled", "field_value": False},
24+
],
25+
indirect=True,
26+
)
27+
def test_catalog_source_merge(
28+
self,
29+
sparse_override_catalog_source: dict,
30+
model_catalog_rest_url: list[str],
31+
model_registry_rest_headers: dict[str, str],
32+
):
33+
"""
34+
RHOAIENG-41738: Test that a sparse override in custom ConfigMap successfully overrides
35+
specific fields while preserving unspecified fields.
36+
"""
37+
catalog_id = sparse_override_catalog_source["catalog_id"]
38+
field_name = sparse_override_catalog_source["field_name"]
39+
field_value = sparse_override_catalog_source["field_value"]
40+
original_catalog = sparse_override_catalog_source["original_catalog"]
41+
42+
# Query sources endpoint to get the merged result
43+
response = execute_get_command(url=f"{model_catalog_rest_url[0]}sources", headers=model_registry_rest_headers)
44+
items = response.get("items", [])
45+
LOGGER.info(f"API response items: {items}")
46+
47+
# Find the catalog we're testing
48+
merged_catalog = next((item for item in items if item.get("id") == catalog_id), None)
49+
assert merged_catalog is not None, f"Catalog '{catalog_id}' not found in sources"
50+
51+
# Validate the overridden field has the new value
52+
assert merged_catalog.get(field_name) == field_value, (
53+
f"Field '{field_name}' should have value {field_value}, got: {merged_catalog.get(field_name)}"
54+
)
55+
56+
# Determine fields to check for preservation (exclude the overridden field)
57+
fields_to_check = set(original_catalog.keys()) - {field_name}
58+
59+
# Special case: when enabled=False, status automatically changes to "disabled"
60+
if field_name == "enabled" and field_value is False:
61+
assert merged_catalog.get("status") == "disabled", (
62+
f"Status should be 'disabled' when enabled=False, got: {merged_catalog.get('status')}"
63+
)
64+
fields_to_check.discard("status")
65+
66+
# Validate all other fields preserve original values
67+
for orig_field in fields_to_check:
68+
orig_value = original_catalog[orig_field]
69+
assert merged_catalog.get(orig_field) == orig_value, (
70+
f"Field '{orig_field}' should preserve original value {orig_value}, "
71+
f"got: {merged_catalog.get(orig_field)}"
72+
)
73+
74+
LOGGER.info(
75+
f"Sparse override merge validated for '{catalog_id}' - "
76+
f"Overridden field '{field_name}': {field_value} | "
77+
f"Preserved {len(original_catalog) - 1} other fields"
78+
)

tests/model_registry/model_catalog/catalog_config/test_default_model_catalog.py

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
from ocp_resources.route import Route
1616
from ocp_resources.service import Service
1717

18-
from tests.model_registry.constants import DEFAULT_CUSTOM_MODEL_CATALOG, DEFAULT_MODEL_CATALOG_CM
18+
from tests.model_registry.constants import DEFAULT_MODEL_CATALOG_CM, DEFAULT_CUSTOM_MODEL_CATALOG
1919
from tests.model_registry.model_catalog.constants import REDHAT_AI_CATALOG_ID, CATALOG_CONTAINER, DEFAULT_CATALOGS
2020
from tests.model_registry.model_catalog.catalog_config.utils import (
2121
validate_model_catalog_enabled,
@@ -35,7 +35,6 @@
3535
"model_registry_namespace",
3636
"original_user",
3737
"test_idp_user",
38-
"enabled_model_catalog_config_map",
3938
)
4039
]
4140

@@ -47,7 +46,7 @@ class TestModelCatalogGeneral:
4746
[
4847
pytest.param(
4948
{"configmap_name": DEFAULT_CUSTOM_MODEL_CATALOG},
50-
2,
49+
0,
5150
False,
5251
id="test_model_catalog_sources_configmap_install",
5352
marks=pytest.mark.install,
@@ -168,7 +167,6 @@ class TestModelCatalogDefault:
168167
def test_model_catalog_default_catalog_sources(
169168
self,
170169
pytestconfig: pytest.Config,
171-
enabled_model_catalog_config_map: ConfigMap,
172170
test_idp_user: UserTestSession,
173171
model_catalog_rest_url: list[str],
174172
user_token_for_api_calls: str,
@@ -204,7 +202,6 @@ def test_model_catalog_default_catalog_sources(
204202

205203
def test_model_default_catalog_get_models_by_source(
206204
self: Self,
207-
enabled_model_catalog_config_map: ConfigMap,
208205
model_catalog_rest_url: list[str],
209206
randomly_picked_model_from_catalog_api_by_source: tuple[dict[Any, Any], str, str],
210207
):
@@ -217,7 +214,6 @@ def test_model_default_catalog_get_models_by_source(
217214

218215
def test_model_default_catalog_get_model_by_name(
219216
self: Self,
220-
enabled_model_catalog_config_map: ConfigMap,
221217
model_catalog_rest_url: list[str],
222218
user_token_for_api_calls: str,
223219
randomly_picked_model_from_catalog_api_by_source: tuple[dict[Any, Any], str, str],
@@ -235,7 +231,6 @@ def test_model_default_catalog_get_model_by_name(
235231

236232
def test_model_default_catalog_get_model_artifact(
237233
self: Self,
238-
enabled_model_catalog_config_map: ConfigMap,
239234
model_catalog_rest_url: list[str],
240235
user_token_for_api_calls: str,
241236
randomly_picked_model_from_catalog_api_by_source: tuple[dict[Any, Any], str, str],
@@ -261,7 +256,6 @@ class TestModelCatalogDefaultData:
261256

262257
def test_model_default_catalog_number_of_models(
263258
self: Self,
264-
enabled_model_catalog_config_map: ConfigMap,
265259
default_catalog_api_response: dict[Any, Any],
266260
default_model_catalog_yaml_content: dict[Any, Any],
267261
):
@@ -278,7 +272,6 @@ def test_model_default_catalog_number_of_models(
278272

279273
def test_model_default_catalog_correspondence_of_model_name(
280274
self: Self,
281-
enabled_model_catalog_config_map: ConfigMap,
282275
default_catalog_api_response: dict[Any, Any],
283276
default_model_catalog_yaml_content: dict[Any, Any],
284277
catalog_openapi_schema: dict[Any, Any],
@@ -343,7 +336,6 @@ def test_model_default_catalog_correspondence_of_model_name(
343336

344337
def test_model_default_catalog_random_artifact(
345338
self: Self,
346-
enabled_model_catalog_config_map: ConfigMap,
347339
default_model_catalog_yaml_content: dict[Any, Any],
348340
model_catalog_rest_url: list[str],
349341
model_registry_rest_headers: dict[str, str],

tests/model_registry/model_catalog/catalog_config/test_model_catalog_negative.py

Lines changed: 61 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ def test_modify_default_catalog_configmap_reconciles(
8383
type: hf
8484
enabled: true
8585
""",
86-
"includedModels cannot be empty for HuggingFace catalog",
86+
"includedModels cannot be empty for Hugging Face catalog",
8787
id="test_hf_source_no_include_model",
8888
),
8989
pytest.param(
@@ -94,11 +94,67 @@ def test_modify_default_catalog_configmap_reconciles(
9494
type: hf
9595
enabled: true
9696
includedModels:
97-
- ibm-granite/*
97+
- abc-random*
9898
""",
99-
"HuggingFace API returned status 401",
100-
id="test_hf_source_unauthorized",
101-
marks=pytest.mark.xfail(reason="RHOAIENG-42213 is causing this failure"),
99+
'failed to expand model patterns: wildcard pattern "abc-random*" is not supported - '
100+
"Hugging Face requires a specific organization",
101+
id="test_hf_source_invalid_organization",
102+
),
103+
pytest.param(
104+
"""
105+
catalogs:
106+
- name: HuggingFace Hub
107+
id: error_catalog
108+
type: hf
109+
enabled: true
110+
includedModels:
111+
- '*'
112+
""",
113+
'failed to expand model patterns: wildcard pattern "*" is not supported - '
114+
"Hugging Face requires a specific organization",
115+
id="test_hf_source_global_wildcard",
116+
),
117+
pytest.param(
118+
"""
119+
catalogs:
120+
- name: HuggingFace Hub
121+
id: error_catalog
122+
type: hf
123+
enabled: true
124+
includedModels:
125+
- '*/*'
126+
""",
127+
'failed to expand model patterns: wildcard pattern "*/*" is not supported - '
128+
"Hugging Face requires a specific organization",
129+
id="test_hf_source_global_org_wildcard",
130+
),
131+
pytest.param(
132+
"""
133+
catalogs:
134+
- name: HuggingFace Hub
135+
id: error_catalog
136+
type: hf
137+
enabled: true
138+
includedModels:
139+
- 'RedHatAI/'
140+
""",
141+
'failed to expand model patterns: wildcard pattern "RedHatAI/" is not supported - '
142+
"Hugging Face requires a specific organization",
143+
id="test_hf_source_empty_model_name",
144+
),
145+
pytest.param(
146+
"""
147+
catalogs:
148+
- name: HuggingFace Hub
149+
id: error_catalog
150+
type: hf
151+
enabled: true
152+
includedModels:
153+
- '*RedHatAI*'
154+
""",
155+
'failed to expand model patterns: wildcard pattern "*RedHatAI*" is not supported - '
156+
"Hugging Face requires a specific organization",
157+
id="test_hf_source_multiple_wildcards",
102158
),
103159
],
104160
indirect=["updated_catalog_config_map_scope_function"],

tests/model_registry/model_catalog/conftest.py

Lines changed: 49 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,9 @@
1818
REDHAT_AI_CATALOG_ID,
1919
DEFAULT_CATALOGS,
2020
)
21-
from tests.model_registry.model_catalog.utils import get_models_from_catalog_api, get_catalog_url_and_headers
21+
from tests.model_registry.model_catalog.utils import get_models_from_catalog_api
2222
from tests.model_registry.constants import (
2323
CUSTOM_CATALOG_ID1,
24-
DEFAULT_MODEL_CATALOG_CM,
2524
DEFAULT_CUSTOM_MODEL_CATALOG,
2625
)
2726
from tests.model_registry.utils import (
@@ -39,55 +38,63 @@
3938
LOGGER = get_logger(name=__name__)
4039

4140

42-
@pytest.fixture(scope="session")
43-
def enabled_model_catalog_config_map(
44-
admin_client: DynamicClient,
41+
@pytest.fixture()
42+
def sparse_override_catalog_source(
43+
request: pytest.FixtureRequest,
44+
admin_client,
4545
model_registry_namespace: str,
46-
current_client_token: str,
47-
) -> Generator[ConfigMap, None, None]:
46+
model_catalog_rest_url: list[str],
47+
model_registry_rest_headers: dict[str, str],
48+
) -> Generator[dict, None, None]:
4849
"""
49-
Enable all catalogs in the default model catalog configmap
50+
Creates a sparse override for an existing default catalog source.
51+
52+
Requires parameterization via request.param dict containing:
53+
- "id": catalog ID to override (required)
54+
- "field_name": name of the field to override (required)
55+
- "field_value": value for the field (required)
5056
"""
51-
# Get operator-managed default sources ConfigMap
52-
default_sources_cm = ConfigMap(
53-
name=DEFAULT_MODEL_CATALOG_CM, client=admin_client, namespace=model_registry_namespace, ensure_exists=True
57+
# Get fields from pytest param
58+
param = getattr(request, "param", None)
59+
assert param, "sparse_override_catalog_source requires request.param dict"
60+
61+
catalog_id = param["id"]
62+
field_name = param["field_name"]
63+
field_value = param["field_value"]
64+
65+
# Capture CURRENT catalog state from API before applying sparse override
66+
response = execute_get_command(url=f"{model_catalog_rest_url[0]}sources", headers=model_registry_rest_headers)
67+
items = response.get("items", [])
68+
original_catalog = next((item for item in items if item.get("id") == catalog_id), None)
69+
assert original_catalog is not None, f"Original catalog '{catalog_id}' not found in sources"
70+
71+
# Create sparse override YAML with only id and the field to override
72+
catalog_override = {"id": catalog_id, field_name: field_value}
73+
sparse_catalog_yaml = yaml.dump(
74+
{"catalogs": [catalog_override]},
75+
default_flow_style=False,
5476
)
5577

56-
# Get the sources.yaml content from default sources
57-
default_sources_yaml = default_sources_cm.instance.data.get("sources.yaml", "")
58-
59-
# Parse the YAML and extract only catalogs, enabling each one
60-
parsed_yaml = yaml.safe_load(default_sources_yaml)
61-
if not parsed_yaml or "catalogs" not in parsed_yaml:
62-
raise RuntimeError("No catalogs found in default sources ConfigMap")
63-
64-
for catalog in parsed_yaml["catalogs"]:
65-
catalog["enabled"] = True
66-
enabled_yaml_dict = {"catalogs": parsed_yaml["catalogs"]}
67-
enabled_sources_yaml = yaml.dump(enabled_yaml_dict, default_flow_style=False, sort_keys=False)
68-
69-
LOGGER.info("Adding enabled catalogs to model-catalog-sources ConfigMap")
70-
71-
# Get user-managed sources ConfigMap
72-
user_sources_cm = ConfigMap(
73-
name=DEFAULT_CUSTOM_MODEL_CATALOG, client=admin_client, namespace=model_registry_namespace, ensure_exists=True
78+
# Write sparse override to custom ConfigMap
79+
sources_cm = ConfigMap(
80+
name=DEFAULT_CUSTOM_MODEL_CATALOG,
81+
client=admin_client,
82+
namespace=model_registry_namespace,
7483
)
84+
patches = {"data": {"sources.yaml": sparse_catalog_yaml}}
7585

76-
patches = {"data": {"sources.yaml": enabled_sources_yaml}}
77-
78-
with ResourceEditor(patches={user_sources_cm: patches}):
79-
# Wait for the model catalog pod to be ready
86+
with ResourceEditor(patches={sources_cm: patches}):
8087
is_model_catalog_ready(client=admin_client, model_registry_namespace=model_registry_namespace)
88+
wait_for_model_catalog_api(url=model_catalog_rest_url[0], headers=model_registry_rest_headers)
89+
yield {
90+
"catalog_id": catalog_id,
91+
"field_name": field_name,
92+
"field_value": field_value,
93+
"original_catalog": original_catalog,
94+
}
8195

82-
# Get the model catalog URL and headers and wait for the API to be fully ready
83-
catalog_url, headers = get_catalog_url_and_headers(
84-
admin_client=admin_client,
85-
model_registry_namespace=model_registry_namespace,
86-
token=current_client_token,
87-
)
88-
wait_for_model_catalog_api(url=catalog_url, headers=headers)
89-
90-
yield user_sources_cm
96+
is_model_catalog_ready(client=admin_client, model_registry_namespace=model_registry_namespace)
97+
wait_for_model_catalog_api(url=model_catalog_rest_url[0], headers=model_registry_rest_headers)
9198

9299

93100
@pytest.fixture(scope="class")

tests/model_registry/model_catalog/constants.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,14 @@
2525
"type": "yaml",
2626
"properties": {"yamlCatalogPath": "/shared-data/models-catalog.yaml"},
2727
"labels": [REDHAT_AI_CATALOG_NAME],
28+
"enabled": True,
2829
},
2930
"redhat_ai_validated_models": {
3031
"name": REDHAT_AI_VALIDATED_CATALOG_NAME,
3132
"type": "yaml",
3233
"properties": {"yamlCatalogPath": "/shared-data/validated-models-catalog.yaml"},
3334
"labels": [REDHAT_AI_VALIDATED_CATALOG_NAME],
35+
"enabled": True,
3436
},
3537
}
3638
REDHAT_AI_CATALOG_ID: str = "redhat_ai_models"

0 commit comments

Comments
 (0)