Skip to content

Commit 7fc9d7f

Browse files
authored
Merge branch 'main' into feature/maas-model-ref-update
2 parents cec1cd9 + 4cd216a commit 7fc9d7f

37 files changed

+1623
-1093
lines changed

pytest.ini

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,11 @@ markers =
1010
parallel: marks tests that can run in parallel along with pytest-xdist
1111

1212
# CI
13-
smoke: Mark tests as smoke tests; covers core functionality of the product. Aims to ensure that the build is stable enough for further testing.
13+
smoke: Mark tests as smoke tests; very high critical priority tests. Covers core functionality of the product. Aims to ensure that the build is stable enough for further testing.
1414
sanity: Mark tests as sanity tests. Aims to verify that specific functionality is working as expected.
15-
tier1: Mark tests as tier1. Aims to cover frequently used functionality of the product and basic user flows.
16-
tier2: Mark tests as tier2. Aims to cover more advanced functionality of the product.
15+
tier1: Mark tests as tier1. High-priority tests.
16+
tier2: Mark tests as tier2. Medium/low-priority positive tests.
17+
tier3: Mark tests as tier3. Negative and destructive tests.
1718
slow: Mark tests which take more than 10 minutes as slow tests.
1819
pre_upgrade: Mark tests which should be run before upgrading the product.
1920
post_upgrade: Mark tests which should be run after upgrading the product.

tests/conftest.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -676,6 +676,12 @@ def cluster_sanity_scope_session(
676676
dsc_resource: DataScienceCluster,
677677
junitxml_plugin: Callable[[str, object], None],
678678
) -> None:
679+
# Skip cluster sanity check when running tests that have cluster_health or operator_health markers
680+
selected_markers = {mark.name for item in request.session.items for mark in item.iter_markers()}
681+
if {"cluster_health", "operator_health"} & selected_markers:
682+
LOGGER.info("Skipping cluster sanity check because selected tests include cluster/operator health")
683+
return
684+
679685
verify_cluster_sanity(
680686
request=request,
681687
nodes=nodes,

tests/model_registry/conftest.py

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
from typing import Any
55

66
import pytest
7+
import yaml
78
from kubernetes.dynamic import DynamicClient
89
from ocp_resources.config_map import ConfigMap
910
from ocp_resources.data_science_cluster import DataScienceCluster
@@ -25,18 +26,26 @@
2526
from tests.model_registry.constants import (
2627
DB_BASE_RESOURCES_NAME,
2728
DB_RESOURCE_NAME,
29+
DEFAULT_CUSTOM_MODEL_CATALOG,
2830
KUBERBACPROXY_STR,
2931
MR_INSTANCE_BASE_NAME,
3032
MR_INSTANCE_NAME,
3133
MR_OPERATOR_NAME,
3234
)
35+
from tests.model_registry.mcp_servers.constants import (
36+
MCP_CATALOG_API_PATH,
37+
MCP_CATALOG_SOURCE,
38+
MCP_SERVERS_YAML,
39+
)
3340
from tests.model_registry.utils import (
3441
generate_namespace_name,
3542
get_byoidc_user_credentials,
3643
get_model_registry_metadata_resources,
3744
get_model_registry_objects,
3845
get_rest_headers,
3946
wait_for_default_resource_cleanedup,
47+
wait_for_mcp_catalog_api,
48+
wait_for_model_catalog_pod_ready_after_deletion,
4049
)
4150
from utilities.constants import DscComponents, Labels
4251
from utilities.general import (
@@ -466,3 +475,54 @@ def model_catalog_routes(admin_client: DynamicClient, model_registry_namespace:
466475
return list(
467476
Route.get(namespace=model_registry_namespace, label_selector="component=model-catalog", client=admin_client)
468477
)
478+
479+
480+
@pytest.fixture(scope="class")
481+
def mcp_catalog_rest_urls(model_registry_namespace: str, model_catalog_routes: list[Route]) -> list[str]:
482+
"""Build MCP catalog REST URL from existing model catalog routes."""
483+
assert model_catalog_routes, f"Model catalog routes do not exist in {model_registry_namespace}"
484+
return [f"https://{route.instance.spec.host}:443{MCP_CATALOG_API_PATH}" for route in model_catalog_routes]
485+
486+
487+
@pytest.fixture(scope="class")
488+
def mcp_servers_configmap_patch(
489+
admin_client: DynamicClient,
490+
model_registry_namespace: str,
491+
mcp_catalog_rest_urls: list[str],
492+
model_registry_rest_headers: dict[str, str],
493+
) -> Generator[None]:
494+
"""
495+
Class-scoped fixture that patches the model-catalog-sources ConfigMap
496+
497+
Sets two keys in the ConfigMap data:
498+
- sources.yaml: catalog source definition pointing to the MCP servers YAML
499+
- mcp-servers.yaml: the actual MCP server definitions
500+
"""
501+
catalog_config_map = ConfigMap(
502+
name=DEFAULT_CUSTOM_MODEL_CATALOG,
503+
client=admin_client,
504+
namespace=model_registry_namespace,
505+
)
506+
507+
current_data = yaml.safe_load(catalog_config_map.instance.data.get("sources.yaml", "{}") or "{}")
508+
if "mcp_catalogs" not in current_data:
509+
current_data["mcp_catalogs"] = []
510+
current_data["mcp_catalogs"].append(MCP_CATALOG_SOURCE)
511+
512+
patches = {
513+
"data": {
514+
"sources.yaml": yaml.dump(current_data, default_flow_style=False),
515+
"mcp-servers.yaml": MCP_SERVERS_YAML,
516+
}
517+
}
518+
519+
with ResourceEditor(patches={catalog_config_map: patches}):
520+
wait_for_model_catalog_pod_ready_after_deletion(
521+
client=admin_client, model_registry_namespace=model_registry_namespace
522+
)
523+
wait_for_mcp_catalog_api(url=mcp_catalog_rest_urls[0], headers=model_registry_rest_headers)
524+
yield
525+
526+
wait_for_model_catalog_pod_ready_after_deletion(
527+
client=admin_client, model_registry_namespace=model_registry_namespace
528+
)

tests/model_registry/image_validation/test_verify_rhoai_images.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
from utilities.constants import Labels
2020

2121
LOGGER = get_logger(name=__name__)
22-
pytestmark = [pytest.mark.downstream_only, pytest.mark.skip_must_gather, pytest.mark.smoke]
22+
pytestmark = [pytest.mark.downstream_only, pytest.mark.skip_must_gather]
2323

2424

2525
class TestAIHubResourcesImages:
@@ -28,15 +28,15 @@ class TestAIHubResourcesImages:
2828
[
2929
pytest.param(
3030
{"namespace": py_config["model_registry_namespace"], "label_selector": "component=model-catalog"},
31-
marks=pytest.mark.smoke,
31+
marks=pytest.mark.tier1,
3232
id="test_model_catalog_pods_images",
3333
),
3434
pytest.param(
3535
{
3636
"namespace": py_config["applications_namespace"],
3737
"label_selector": f"{Labels.OpenDataHubIo.NAME}={MR_OPERATOR_NAME}",
3838
},
39-
marks=pytest.mark.smoke,
39+
marks=pytest.mark.tier1,
4040
id="test_model_registry_operator_pods_images",
4141
),
4242
],
@@ -54,7 +54,7 @@ def test_verify_pod_images(
5454
@pytest.mark.parametrize(
5555
"model_registry_metadata_db_resources, model_registry_instance, model_registry_instance_pods_by_label",
5656
[
57-
pytest.param({}, {}, {"label_selectors": [f"app={MR_INSTANCE_NAME}"]}),
57+
pytest.param({}, {}, {"label_selectors": [f"app={MR_INSTANCE_NAME}"]}, marks=pytest.mark.smoke),
5858
pytest.param(
5959
{"db_name": "default"},
6060
{"db_name": "default"},
@@ -64,6 +64,7 @@ def test_verify_pod_images(
6464
f"app.kubernetes.io/name={MR_POSTGRES_DEPLOYMENT_NAME_STR}",
6565
]
6666
},
67+
marks=pytest.mark.tier2,
6768
),
6869
],
6970
indirect=True,

tests/model_registry/mcp_servers/conftest.py

Lines changed: 1 addition & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -1,57 +1,29 @@
11
from collections.abc import Generator
22

33
import pytest
4-
import requests
54
import yaml
65
from kubernetes.dynamic import DynamicClient
7-
from kubernetes.dynamic.exceptions import ResourceNotFoundError
86
from ocp_resources.config_map import ConfigMap
97
from ocp_resources.resource import ResourceEditor
10-
from ocp_resources.route import Route
118
from simple_logger.logger import get_logger
12-
from timeout_sampler import retry
139

1410
from tests.model_registry.constants import DEFAULT_CUSTOM_MODEL_CATALOG
1511
from tests.model_registry.mcp_servers.constants import (
16-
MCP_CATALOG_API_PATH,
1712
MCP_CATALOG_INVALID_SOURCE,
1813
MCP_CATALOG_SOURCE,
1914
MCP_CATALOG_SOURCE2,
2015
MCP_SERVERS_YAML,
2116
MCP_SERVERS_YAML2,
2217
)
2318
from tests.model_registry.utils import (
24-
TransientUnauthorizedError,
25-
execute_get_call,
2619
execute_get_command,
20+
wait_for_mcp_catalog_api,
2721
wait_for_model_catalog_pod_ready_after_deletion,
2822
)
2923

3024
LOGGER = get_logger(name=__name__)
3125

3226

33-
@pytest.fixture(scope="class")
34-
def mcp_catalog_rest_urls(model_registry_namespace: str, model_catalog_routes: list[Route]) -> list[str]:
35-
"""Build MCP catalog REST URL from existing model catalog routes."""
36-
assert model_catalog_routes, f"Model catalog routes do not exist in {model_registry_namespace}"
37-
return [f"https://{route.instance.spec.host}:443{MCP_CATALOG_API_PATH}" for route in model_catalog_routes]
38-
39-
40-
@retry(
41-
wait_timeout=90,
42-
sleep=5,
43-
exceptions_dict={ResourceNotFoundError: [], TransientUnauthorizedError: []},
44-
)
45-
def wait_for_mcp_catalog_api(url: str, headers: dict[str, str]) -> requests.Response:
46-
"""Wait for MCP catalog API to be ready and returning MCP server data."""
47-
LOGGER.info(f"Waiting for MCP catalog API at {url}mcp_servers")
48-
response = execute_get_call(url=f"{url}mcp_servers", headers=headers)
49-
data = response.json()
50-
if not data.get("items"):
51-
raise ResourceNotFoundError("MCP catalog API returned empty items, catalog data not yet loaded")
52-
return response
53-
54-
5527
@pytest.fixture(scope="class")
5628
def mcp_servers_response(
5729
mcp_catalog_rest_urls: list[str],
@@ -64,50 +36,6 @@ def mcp_servers_response(
6436
)
6537

6638

67-
@pytest.fixture(scope="class")
68-
def mcp_servers_configmap_patch(
69-
admin_client: DynamicClient,
70-
model_registry_namespace: str,
71-
mcp_catalog_rest_urls: list[str],
72-
model_registry_rest_headers: dict[str, str],
73-
) -> Generator[None]:
74-
"""
75-
Class-scoped fixture that patches the model-catalog-sources ConfigMap
76-
77-
Sets two keys in the ConfigMap data:
78-
- sources.yaml: catalog source definition pointing to the MCP servers YAML
79-
- mcp-servers.yaml: the actual MCP server definitions
80-
"""
81-
catalog_config_map = ConfigMap(
82-
name=DEFAULT_CUSTOM_MODEL_CATALOG,
83-
client=admin_client,
84-
namespace=model_registry_namespace,
85-
)
86-
87-
current_data = yaml.safe_load(catalog_config_map.instance.data.get("sources.yaml", "{}") or "{}")
88-
if "mcp_catalogs" not in current_data:
89-
current_data["mcp_catalogs"] = []
90-
current_data["mcp_catalogs"].append(MCP_CATALOG_SOURCE)
91-
92-
patches = {
93-
"data": {
94-
"sources.yaml": yaml.dump(current_data, default_flow_style=False),
95-
"mcp-servers.yaml": MCP_SERVERS_YAML,
96-
}
97-
}
98-
99-
with ResourceEditor(patches={catalog_config_map: patches}):
100-
wait_for_model_catalog_pod_ready_after_deletion(
101-
client=admin_client, model_registry_namespace=model_registry_namespace
102-
)
103-
wait_for_mcp_catalog_api(url=mcp_catalog_rest_urls[0], headers=model_registry_rest_headers)
104-
yield
105-
106-
wait_for_model_catalog_pod_ready_after_deletion(
107-
client=admin_client, model_registry_namespace=model_registry_namespace
108-
)
109-
110-
11139
@pytest.fixture(scope="class")
11240
def mcp_multi_source_configmap_patch(
11341
admin_client: DynamicClient,

tests/model_registry/mcp_servers/test_data_integrity.py

Lines changed: 0 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
from tests.model_registry.mcp_servers.constants import (
77
EXPECTED_MCP_SERVER_NAMES,
88
EXPECTED_MCP_SERVER_TIMESTAMPS,
9-
EXPECTED_MCP_SERVER_TOOL_COUNTS,
109
EXPECTED_MCP_SERVER_TOOLS,
1110
)
1211
from tests.model_registry.utils import execute_get_command
@@ -30,9 +29,6 @@ def test_mcp_servers_loaded(
3029
assert server["createTimeSinceEpoch"] == expected["createTimeSinceEpoch"]
3130
assert server["lastUpdateTimeSinceEpoch"] == expected["lastUpdateTimeSinceEpoch"]
3231

33-
@pytest.mark.xfail(
34-
reason="RHOAIENG-51765: Tool name returned as {server}@{version}:{tool} instead of YAML-defined name"
35-
)
3632
def test_mcp_server_tools_loaded(
3733
self: Self,
3834
mcp_catalog_rest_urls: list[str],
@@ -50,17 +46,3 @@ def test_mcp_server_tools_loaded(
5046
assert server["toolCount"] == len(expected_tool_names)
5147
actual_tool_names = [tool["name"] for tool in server["tools"]]
5248
assert sorted(actual_tool_names) == sorted(expected_tool_names)
53-
54-
@pytest.mark.xfail(reason="RHOAIENG-51764: toolCount is 0 when not passing includeTools=true")
55-
def test_mcp_server_tool_count_without_include(
56-
self: Self,
57-
mcp_servers_response: dict[str, Any],
58-
):
59-
"""Verify that toolCount reflects actual tools even when tools are not included."""
60-
for server in mcp_servers_response.get("items", []):
61-
name = server["name"]
62-
expected_count = EXPECTED_MCP_SERVER_TOOL_COUNTS[name]
63-
actual_count = server.get("toolCount", 0)
64-
assert actual_count == expected_count, (
65-
f"Server '{name}': expected toolCount {expected_count}, got {actual_count}"
66-
)

0 commit comments

Comments
 (0)