Skip to content

Commit 8a28c9b

Browse files
ruivieirakpunwatkdbasunag
authored
feat(evalhub): add EvalHub service health check tests (#1145)
* feat(evalhub): add EvalHub service health check tests * fix: EvalHub resource generated from CRD --------- Co-authored-by: Karishma Punwatkar <kpunwatk@redhat.com> Co-authored-by: Debarati Basu-Nag <dbasunag@redhat.com>
1 parent 2017513 commit 8a28c9b

File tree

6 files changed

+210
-0
lines changed

6 files changed

+210
-0
lines changed

tests/model_explainability/evalhub/__init__.py

Whitespace-only changes.
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
from collections.abc import Generator
2+
from typing import Any
3+
4+
import pytest
5+
from kubernetes.dynamic import DynamicClient
6+
from ocp_resources.deployment import Deployment
7+
from ocp_resources.namespace import Namespace
8+
from ocp_resources.route import Route
9+
from simple_logger.logger import get_logger
10+
11+
from utilities.certificates_utils import create_ca_bundle_file
12+
from utilities.constants import Timeout
13+
from utilities.resources.evalhub import EvalHub
14+
15+
LOGGER = get_logger(name=__name__)
16+
17+
18+
@pytest.fixture(scope="class")
19+
def evalhub_cr(
20+
admin_client: DynamicClient,
21+
model_namespace: Namespace,
22+
) -> Generator[EvalHub, Any, Any]:
23+
"""Create an EvalHub custom resource and wait for it to be ready."""
24+
with EvalHub(
25+
client=admin_client,
26+
name="evalhub",
27+
namespace=model_namespace.name,
28+
wait_for_resource=True,
29+
) as evalhub:
30+
yield evalhub
31+
32+
33+
@pytest.fixture(scope="class")
34+
def evalhub_deployment(
35+
admin_client: DynamicClient,
36+
model_namespace: Namespace,
37+
evalhub_cr: EvalHub,
38+
) -> Deployment:
39+
"""Wait for the EvalHub deployment to become available."""
40+
deployment = Deployment(
41+
client=admin_client,
42+
name=evalhub_cr.name,
43+
namespace=model_namespace.name,
44+
)
45+
deployment.wait_for_replicas(timeout=Timeout.TIMEOUT_5MIN)
46+
return deployment
47+
48+
49+
@pytest.fixture(scope="class")
50+
def evalhub_route(
51+
admin_client: DynamicClient,
52+
model_namespace: Namespace,
53+
evalhub_deployment: Deployment,
54+
) -> Route:
55+
"""Get the Route created by the operator for the EvalHub service."""
56+
return Route(
57+
client=admin_client,
58+
name=evalhub_deployment.name,
59+
namespace=model_namespace.name,
60+
ensure_exists=True,
61+
)
62+
63+
64+
@pytest.fixture(scope="class")
65+
def evalhub_ca_bundle_file(
66+
admin_client: DynamicClient,
67+
) -> str:
68+
"""Create a CA bundle file for verifying the EvalHub route TLS certificate."""
69+
return create_ca_bundle_file(client=admin_client, ca_type="openshift")
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
EVALHUB_SERVICE_NAME: str = "evalhub"
2+
EVALHUB_SERVICE_PORT: int = 8443
3+
EVALHUB_CONTAINER_PORT: int = 8080
4+
EVALHUB_HEALTH_PATH: str = "/api/v1/health"
5+
EVALHUB_PROVIDERS_PATH: str = "/api/v1/evaluations/providers"
6+
EVALHUB_HEALTH_STATUS_HEALTHY: str = "healthy"
7+
8+
EVALHUB_APP_LABEL: str = "eval-hub"
9+
10+
# CRD details
11+
EVALHUB_API_GROUP: str = "trustyai.opendatahub.io"
12+
EVALHUB_API_VERSION: str = "v1alpha1"
13+
EVALHUB_KIND: str = "EvalHub"
14+
EVALHUB_PLURAL: str = "evalhubs"
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import pytest
2+
from ocp_resources.route import Route
3+
4+
from tests.model_explainability.evalhub.utils import validate_evalhub_health
5+
6+
7+
@pytest.mark.parametrize(
8+
"model_namespace",
9+
[
10+
pytest.param(
11+
{"name": "test-evalhub-health"},
12+
),
13+
],
14+
indirect=True,
15+
)
16+
@pytest.mark.smoke
17+
@pytest.mark.model_explainability
18+
class TestEvalHubHealth:
19+
"""Tests for basic EvalHub service health and availability."""
20+
21+
def test_evalhub_health_endpoint(
22+
self,
23+
current_client_token: str,
24+
evalhub_ca_bundle_file: str,
25+
evalhub_route: Route,
26+
) -> None:
27+
"""Verify the EvalHub service responds with healthy status via kube-rbac-proxy."""
28+
validate_evalhub_health(
29+
host=evalhub_route.host,
30+
token=current_client_token,
31+
ca_bundle_file=evalhub_ca_bundle_file,
32+
)
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import requests
2+
from simple_logger.logger import get_logger
3+
4+
from tests.model_explainability.evalhub.constants import (
5+
EVALHUB_HEALTH_PATH,
6+
EVALHUB_HEALTH_STATUS_HEALTHY,
7+
)
8+
from utilities.guardrails import get_auth_headers
9+
10+
LOGGER = get_logger(name=__name__)
11+
12+
13+
def validate_evalhub_health(
14+
host: str,
15+
token: str,
16+
ca_bundle_file: str,
17+
) -> None:
18+
"""Validate that the EvalHub service health endpoint returns healthy status.
19+
20+
Args:
21+
host: Route host for the EvalHub service.
22+
token: Bearer token for authentication.
23+
ca_bundle_file: Path to CA bundle for TLS verification.
24+
25+
Raises:
26+
AssertionError: If the health check fails.
27+
requests.HTTPError: If the request fails.
28+
"""
29+
url = f"https://{host}{EVALHUB_HEALTH_PATH}"
30+
LOGGER.info(f"Checking EvalHub health at {url}")
31+
32+
response = requests.get(
33+
url=url,
34+
headers=get_auth_headers(token=token),
35+
verify=ca_bundle_file,
36+
timeout=10,
37+
)
38+
response.raise_for_status()
39+
40+
data = response.json()
41+
LOGGER.info(f"EvalHub health response: {data}")
42+
43+
assert "status" in data, "Health response missing 'status' field"
44+
assert data["status"] == EVALHUB_HEALTH_STATUS_HEALTHY, (
45+
f"Expected status '{EVALHUB_HEALTH_STATUS_HEALTHY}', got '{data['status']}'"
46+
)
47+
assert "timestamp" in data, "Health response missing 'timestamp' field"

utilities/resources/evalhub.py

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
# Generated using https://github.com/RedHatQE/openshift-python-wrapper/blob/main/scripts/resource/README.md
2+
3+
4+
from typing import Any
5+
6+
from ocp_resources.resource import NamespacedResource
7+
8+
9+
class EvalHub(NamespacedResource):
10+
"""
11+
EvalHub is the Schema for the evalhubs API
12+
"""
13+
14+
api_group: str = NamespacedResource.ApiGroup.TRUSTYAI_OPENDATAHUB_IO
15+
16+
def __init__(
17+
self,
18+
env: list[Any] | None = None,
19+
replicas: int | None = None,
20+
**kwargs: Any,
21+
) -> None:
22+
r"""
23+
Args:
24+
env (list[Any]): Environment variables for the eval-hub container
25+
26+
replicas (int): Number of replicas for the eval-hub deployment
27+
28+
"""
29+
super().__init__(**kwargs)
30+
31+
self.env = env
32+
self.replicas = replicas
33+
34+
def to_dict(self) -> None:
35+
36+
super().to_dict()
37+
38+
if not self.kind_dict and not self.yaml_file:
39+
self.res["spec"] = {}
40+
_spec = self.res["spec"]
41+
42+
if self.env is not None:
43+
_spec["env"] = self.env
44+
45+
if self.replicas is not None:
46+
_spec["replicas"] = self.replicas
47+
48+
# End of generated code

0 commit comments

Comments
 (0)