Skip to content

Commit 3bb31da

Browse files
authored
Remove RHEC tests and add multiple source tests (#597)
1 parent b9a7046 commit 3bb31da

File tree

4 files changed

+188
-74
lines changed

4 files changed

+188
-74
lines changed

tests/model_registry/model_catalog/conftest.py

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@
88

99
from ocp_resources.route import Route
1010
from tests.model_registry.constants import DEFAULT_MODEL_CATALOG
11-
from tests.model_registry.model_catalog.utils import is_model_catalog_ready, wait_for_model_catalog_api
11+
from tests.model_registry.model_catalog.constants import SAMPLE_MODEL_NAME3, CUSTOM_CATALOG_ID1
12+
from tests.model_registry.model_catalog.utils import is_model_catalog_ready, wait_for_model_catalog_api, get_model_str
1213

1314

1415
@pytest.fixture(scope="class")
@@ -47,7 +48,8 @@ def updated_catalog_config_map(
4748
) -> Generator[ConfigMap, None, None]:
4849
patches = {"data": {"sources.yaml": request.param["sources_yaml"]}}
4950
if "sample_yaml" in request.param:
50-
patches["data"]["sample-catalog.yaml"] = request.param["sample_yaml"]
51+
for key in request.param["sample_yaml"]:
52+
patches["data"][key] = request.param["sample_yaml"][key]
5153

5254
with ResourceEditor(patches={catalog_config_map: patches}):
5355
is_model_catalog_ready(client=admin_client, model_registry_namespace=model_registry_namespace)
@@ -59,3 +61,21 @@ def updated_catalog_config_map(
5961
@pytest.fixture(scope="class")
6062
def expected_catalog_values(request: pytest.FixtureRequest) -> dict[str, str]:
6163
return request.param
64+
65+
66+
@pytest.fixture(scope="function")
67+
def update_configmap_data_add_model(
68+
request: pytest.FixtureRequest,
69+
catalog_config_map: ConfigMap,
70+
model_registry_namespace: str,
71+
admin_client: DynamicClient,
72+
model_catalog_rest_url: list[str],
73+
model_registry_rest_headers: dict[str, str],
74+
) -> Generator[ConfigMap, None, None]:
75+
patches = catalog_config_map.instance.to_dict()
76+
patches["data"][f"{CUSTOM_CATALOG_ID1.replace('_', '-')}.yaml"] += get_model_str(model=SAMPLE_MODEL_NAME3)
77+
with ResourceEditor(patches={catalog_config_map: patches}):
78+
is_model_catalog_ready(client=admin_client, model_registry_namespace=model_registry_namespace)
79+
wait_for_model_catalog_api(url=model_catalog_rest_url[0], headers=model_registry_rest_headers)
80+
yield catalog_config_map
81+
is_model_catalog_ready(client=admin_client, model_registry_namespace=model_registry_namespace)
Lines changed: 11 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,44 +1,15 @@
1-
CUSTOM_ECHO_CATALOG_ID: str = "sample_rhec_catalog"
2-
CUSTOM_MODEL_NAME = "rhelai1/modelcar-granite-7b-starter"
3-
CUSTOM_ECOSYSTEM_CATALOG: str = f"""catalogs:
4-
- name: Red Hat Ecosystem Catalog
5-
id: {CUSTOM_ECHO_CATALOG_ID}
6-
type: rhec
7-
enabled: true
8-
properties:
9-
models:
10-
- {CUSTOM_MODEL_NAME}
11-
"""
12-
EXPECTED_ECHO_CATALOG_VALUES: dict[str, str] = {
13-
"id": CUSTOM_ECHO_CATALOG_ID,
14-
"model_name": f"{CUSTOM_MODEL_NAME}:latest",
15-
}
16-
CUSTOM_CATALOG_ID: str = "sample_custom_catalog"
17-
CUSTOM_CATALOG_WITH_FILE = f"""catalogs:
18-
- name: Sample Catalog
19-
id: {CUSTOM_CATALOG_ID}
20-
type: yaml
21-
enabled: true
22-
properties:
23-
yamlCatalogPath: sample-catalog.yaml
24-
"""
25-
SAMPLE_CATALOG_FILE_NAME = "mistralai/Mistral-7B-Instruct-v0.3"
26-
SAMPLE_CATALOG_YAML = f"""source: Hugging Face
27-
models:
28-
- name: {SAMPLE_CATALOG_FILE_NAME}
29-
description: test description.
30-
readme: |-
31-
# test read me information
32-
provider: Mistral AI
33-
logo: temp placeholder logo
34-
license: apache-2.0
35-
licenseLink: https://www.apache.org/licenses/LICENSE-2.0.txt
36-
libraryName: transformers
37-
artifacts:
38-
- uri: https://huggingface.co/mistralai/Mistral-7B-Instruct-v0.3/resolve/main/consolidated.safetensors
39-
"""
40-
EXPECTED_CUSTOM_CATALOG_VALUES: dict[str, str] = {"id": CUSTOM_CATALOG_ID, "model_name": SAMPLE_CATALOG_FILE_NAME}
1+
CUSTOM_CATALOG_ID1: str = "sample_custom_catalog1"
2+
CUSTOM_CATALOG_ID2: str = "sample_custom_catalog2"
3+
SAMPLE_MODEL_NAME1 = "mistralai/Mistral-7B-Instruct-v0.3"
4+
5+
SAMPLE_MODEL_NAME2 = "mistralai/Devstral-Small-2505"
6+
EXPECTED_CUSTOM_CATALOG_VALUES: list[dict[str, str]] = [{"id": CUSTOM_CATALOG_ID1, "model_name": SAMPLE_MODEL_NAME1}]
7+
MULTIPLE_CUSTOM_CATALOG_VALUES: list[dict[str, str]] = [
8+
{"id": CUSTOM_CATALOG_ID1, "model_name": SAMPLE_MODEL_NAME1},
9+
{"id": CUSTOM_CATALOG_ID2, "model_name": SAMPLE_MODEL_NAME2},
10+
]
4111
DEFAULT_CATALOG_NAME: str = "Default Catalog"
4212
DEFAULT_CATALOG_ID: str = "default_catalog"
4313
CATALOG_TYPE: str = "yaml"
4414
DEFAULT_CATALOG_FILE: str = "/default/default-catalog.yaml"
15+
SAMPLE_MODEL_NAME3 = "mistralai/Ministral-8B-Instruct-2410"
Lines changed: 107 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,49 @@
11
from tests.model_registry.model_catalog.constants import (
2-
CUSTOM_ECOSYSTEM_CATALOG,
3-
CUSTOM_CATALOG_WITH_FILE,
4-
SAMPLE_CATALOG_YAML,
52
EXPECTED_CUSTOM_CATALOG_VALUES,
6-
EXPECTED_ECHO_CATALOG_VALUES,
3+
CUSTOM_CATALOG_ID1,
4+
SAMPLE_MODEL_NAME1,
5+
CUSTOM_CATALOG_ID2,
6+
SAMPLE_MODEL_NAME2,
7+
MULTIPLE_CUSTOM_CATALOG_VALUES,
8+
SAMPLE_MODEL_NAME3,
79
)
810
from ocp_resources.config_map import ConfigMap
911
import pytest
1012
from simple_logger.logger import get_logger
1113
from typing import Self
1214

13-
from tests.model_registry.model_catalog.utils import execute_get_command
15+
from tests.model_registry.model_catalog.utils import (
16+
execute_get_command,
17+
get_catalog_str,
18+
get_sample_yaml_str,
19+
ResourceNotFoundError,
20+
)
1421

1522
LOGGER = get_logger(name=__name__)
1623

1724

1825
@pytest.mark.parametrize(
1926
"updated_catalog_config_map, expected_catalog_values",
2027
[
21-
pytest.param({"sources_yaml": CUSTOM_ECOSYSTEM_CATALOG}, EXPECTED_ECHO_CATALOG_VALUES, id="rhec_test_catalog"),
2228
pytest.param(
23-
{"sources_yaml": CUSTOM_CATALOG_WITH_FILE, "sample_yaml": SAMPLE_CATALOG_YAML},
29+
{
30+
"sources_yaml": get_catalog_str(ids=[CUSTOM_CATALOG_ID1]),
31+
"sample_yaml": {"sample-custom-catalog1.yaml": get_sample_yaml_str(models=[SAMPLE_MODEL_NAME1])},
32+
},
2433
EXPECTED_CUSTOM_CATALOG_VALUES,
2534
id="file_test_catalog",
2635
),
36+
pytest.param(
37+
{
38+
"sources_yaml": get_catalog_str(ids=[CUSTOM_CATALOG_ID1, CUSTOM_CATALOG_ID2]),
39+
"sample_yaml": {
40+
"sample-custom-catalog1.yaml": get_sample_yaml_str(models=[SAMPLE_MODEL_NAME1]),
41+
"sample-custom-catalog2.yaml": get_sample_yaml_str(models=[SAMPLE_MODEL_NAME2]),
42+
},
43+
},
44+
MULTIPLE_CUSTOM_CATALOG_VALUES,
45+
id="file_test_catalog",
46+
),
2747
],
2848
indirect=True,
2949
)
@@ -32,7 +52,7 @@
3252
"updated_catalog_config_map",
3353
)
3454
class TestModelCatalogCustom:
35-
def test_model_custom_catalog_sources(
55+
def test_model_custom_catalog_list_sources(
3656
self: Self,
3757
updated_catalog_config_map: tuple[ConfigMap, str, str],
3858
model_catalog_rest_url: list[str],
@@ -42,14 +62,19 @@ def test_model_custom_catalog_sources(
4262
"""
4363
Validate sources api for model catalog
4464
"""
45-
result = execute_get_command(
46-
url=f"{model_catalog_rest_url[0]}sources",
65+
url = f"{model_catalog_rest_url[0]}sources"
66+
results = execute_get_command(
67+
url=url,
4768
headers=model_registry_rest_headers,
4869
)["items"]
49-
assert len(result) == 1
50-
assert result[0]["id"] == expected_catalog_values["id"]
5170

52-
def test_model_custom_catalog_models(
71+
LOGGER.info(f"Results: for uri {url}: {results}")
72+
assert len(results) == len(expected_catalog_values)
73+
ids_from_query = [result_entry["id"] for result_entry in results]
74+
ids_expected = [expected_entry["id"] for expected_entry in expected_catalog_values]
75+
assert sorted(ids_from_query) == sorted(ids_expected), f"Expected: {expected_catalog_values}. Actual: {results}"
76+
77+
def test_model_custom_catalog_get_models_by_source(
5378
self: Self,
5479
updated_catalog_config_map: tuple[ConfigMap, str, str],
5580
model_catalog_rest_url: list[str],
@@ -59,11 +84,15 @@ def test_model_custom_catalog_models(
5984
"""
6085
Validate models api for model catalog associated with a specific source
6186
"""
62-
result = execute_get_command(
63-
url=f"{model_catalog_rest_url[0]}models?source={expected_catalog_values['id']}",
64-
headers=model_registry_rest_headers,
65-
)["items"]
66-
assert result, f"Expected custom models to be present. Actual: {result}"
87+
for expected_entry in expected_catalog_values:
88+
LOGGER.info(f"Expected entry: {expected_entry}")
89+
url = f"{model_catalog_rest_url[0]}models?source={expected_entry['id']}"
90+
result = execute_get_command(
91+
url=url,
92+
headers=model_registry_rest_headers,
93+
)["items"]
94+
LOGGER.info(f"URL: {url} Result: {result}")
95+
assert result, f"Expected custom models to be present. Actual: {result}"
6796

6897
def test_model_custom_catalog_get_model_by_name(
6998
self: Self,
@@ -75,14 +104,18 @@ def test_model_custom_catalog_get_model_by_name(
75104
"""
76105
Get Model by name associated with a specific source
77106
"""
78-
model_name = expected_catalog_values["model_name"]
79-
result = execute_get_command(
80-
url=f"{model_catalog_rest_url[0]}sources/{expected_catalog_values['id']}/models/{model_name}",
81-
headers=model_registry_rest_headers,
82-
)
83-
assert result["name"] == model_name
107+
for expected_entry in expected_catalog_values:
108+
LOGGER.info(f"Expected entry: {expected_entry}")
109+
model_name = expected_entry["model_name"]
110+
url = f"{model_catalog_rest_url[0]}sources/{expected_entry['id']}/models/{model_name}"
111+
result = execute_get_command(
112+
url=url,
113+
headers=model_registry_rest_headers,
114+
)
115+
LOGGER.info(f"URL: {url} Result: {result}")
116+
assert result["name"] == model_name
84117

85-
def test_model_custom_catalog_get_artifact(
118+
def test_model_custom_catalog_get_model_artifact(
86119
self: Self,
87120
updated_catalog_config_map: tuple[ConfigMap, str, str],
88121
model_catalog_rest_url: list[str],
@@ -92,11 +125,54 @@ def test_model_custom_catalog_get_artifact(
92125
"""
93126
Get Model artifacts for model associated with specific source
94127
"""
95-
model_name = expected_catalog_values["model_name"]
96-
artifacts = execute_get_command(
97-
url=f"{model_catalog_rest_url[0]}sources/{expected_catalog_values['id']}/models/{model_name}/artifacts",
128+
for expected_entry in expected_catalog_values:
129+
LOGGER.info(f"Expected entry: {expected_entry}")
130+
131+
model_name = expected_entry["model_name"]
132+
url = f"{model_catalog_rest_url[0]}sources/{expected_entry['id']}/models/{model_name}/artifacts"
133+
134+
artifacts = execute_get_command(
135+
url=url,
136+
headers=model_registry_rest_headers,
137+
)["items"]
138+
LOGGER.info(f"URL: {url} Result: {artifacts}")
139+
140+
assert artifacts, f"No artifacts found for {model_name}"
141+
assert artifacts[0]["uri"]
142+
143+
@pytest.mark.dependency(name="test_model_custom_catalog_add_model")
144+
def test_model_custom_catalog_add_model(
145+
self: Self,
146+
model_catalog_rest_url: list[str],
147+
model_registry_rest_headers: dict[str, str],
148+
expected_catalog_values: dict[str, str],
149+
update_configmap_data_add_model: dict[str, str],
150+
):
151+
"""
152+
Add a model to a source and ensure it is added to the catalog
153+
"""
154+
url = f"{model_catalog_rest_url[0]}sources/{CUSTOM_CATALOG_ID1}/models/{SAMPLE_MODEL_NAME3}"
155+
result = execute_get_command(
156+
url=url,
98157
headers=model_registry_rest_headers,
99-
)["items"]
158+
)
159+
LOGGER.info(f"URL: {url} Result: {result}")
160+
assert result["name"] == SAMPLE_MODEL_NAME3
100161

101-
assert artifacts, f"No artifacts found for {model_name}"
102-
assert artifacts[0]["uri"]
162+
@pytest.mark.dependency(depends=["test_model_custom_catalog_add_model"])
163+
def test_model_custom_catalog_remove_model(
164+
self: Self,
165+
model_catalog_rest_url: list[str],
166+
model_registry_rest_headers: dict[str, str],
167+
expected_catalog_values: dict[str, str],
168+
):
169+
"""
170+
Add a model to a source and ensure it is added to the catalog
171+
"""
172+
url = f"{model_catalog_rest_url[0]}sources/{CUSTOM_CATALOG_ID1}/models/{SAMPLE_MODEL_NAME3}"
173+
with pytest.raises(ResourceNotFoundError):
174+
result = execute_get_command(
175+
url=url,
176+
headers=model_registry_rest_headers,
177+
)
178+
LOGGER.info(f"URL: {url} Result: {result}")

tests/model_registry/model_catalog/utils.py

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
import requests
88
from timeout_sampler import retry
99

10-
from class_generator.parsers.explain_parser import ResourceNotFoundError
1110
from ocp_resources.pod import Pod
1211
from tests.model_registry.model_catalog.constants import (
1312
DEFAULT_CATALOG_NAME,
@@ -20,6 +19,10 @@
2019
LOGGER = get_logger(name=__name__)
2120

2221

22+
class ResourceNotFoundError(Exception):
23+
pass
24+
25+
2326
def _execute_get_call(url: str, headers: dict[str, str], verify: bool | str = False) -> requests.Response:
2427
resp = requests.get(url=url, headers=headers, verify=verify, timeout=60)
2528
if resp.status_code not in [200, 201]:
@@ -87,3 +90,47 @@ def validate_default_catalog(default_catalog) -> None:
8790
assert default_catalog["id"] == DEFAULT_CATALOG_ID
8891
assert default_catalog["type"] == CATALOG_TYPE
8992
assert default_catalog["properties"].get("yamlCatalogPath") == DEFAULT_CATALOG_FILE
93+
94+
95+
def get_catalog_str(ids: list[str]) -> str:
96+
catalog_str: str = ""
97+
for id in ids:
98+
catalog_str += f"""
99+
- name: Sample Catalog
100+
id: {id}
101+
type: yaml
102+
enabled: true
103+
properties:
104+
yamlCatalogPath: {id.replace("_", "-")}.yaml
105+
"""
106+
return f"""catalogs:
107+
{catalog_str}
108+
"""
109+
110+
111+
def get_sample_yaml_str(models: list[str]) -> str:
112+
model_str: str = ""
113+
for model in models:
114+
model_str += f"""
115+
{get_model_str(model=model)}
116+
"""
117+
return f"""source: Hugging Face
118+
models:
119+
{model_str}
120+
"""
121+
122+
123+
def get_model_str(model: str) -> str:
124+
return f"""
125+
- name: {model}
126+
description: test description.
127+
readme: |-
128+
# test read me information {model}
129+
provider: Mistral AI
130+
logo: temp placeholder logo
131+
license: apache-2.0
132+
licenseLink: https://www.apache.org/licenses/LICENSE-2.0.txt
133+
libraryName: transformers
134+
artifacts:
135+
- uri: https://huggingface.co/{model}/resolve/main/consolidated.safetensors
136+
"""

0 commit comments

Comments
 (0)