Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Empty file.
69 changes: 69 additions & 0 deletions tests/model_explainability/evalhub/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
from collections.abc import Generator
from typing import Any

import pytest
from kubernetes.dynamic import DynamicClient
from ocp_resources.deployment import Deployment
from ocp_resources.namespace import Namespace
from ocp_resources.route import Route
from simple_logger.logger import get_logger

from utilities.certificates_utils import create_ca_bundle_file
from utilities.constants import Timeout
from utilities.resources.evalhub import EvalHub

LOGGER = get_logger(name=__name__)


@pytest.fixture(scope="class")
def evalhub_cr(
admin_client: DynamicClient,
model_namespace: Namespace,
) -> Generator[EvalHub, Any, Any]:
"""Create an EvalHub custom resource and wait for it to be ready."""
with EvalHub(
client=admin_client,
name="evalhub",
namespace=model_namespace.name,
wait_for_resource=True,
) as evalhub:
yield evalhub


@pytest.fixture(scope="class")
def evalhub_deployment(
admin_client: DynamicClient,
model_namespace: Namespace,
evalhub_cr: EvalHub,
) -> Deployment:
"""Wait for the EvalHub deployment to become available."""
deployment = Deployment(
client=admin_client,
name=evalhub_cr.name,
namespace=model_namespace.name,
)
deployment.wait_for_replicas(timeout=Timeout.TIMEOUT_5MIN)
return deployment


@pytest.fixture(scope="class")
def evalhub_route(
admin_client: DynamicClient,
model_namespace: Namespace,
evalhub_deployment: Deployment,
) -> Route:
"""Get the Route created by the operator for the EvalHub service."""
return Route(
client=admin_client,
name=evalhub_deployment.name,
namespace=model_namespace.name,
ensure_exists=True,
)


@pytest.fixture(scope="class")
def evalhub_ca_bundle_file(
admin_client: DynamicClient,
) -> str:
"""Create a CA bundle file for verifying the EvalHub route TLS certificate."""
return create_ca_bundle_file(client=admin_client, ca_type="openshift")
Comment thread
ruivieira marked this conversation as resolved.
14 changes: 14 additions & 0 deletions tests/model_explainability/evalhub/constants.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
EVALHUB_SERVICE_NAME: str = "evalhub"
EVALHUB_SERVICE_PORT: int = 8443
EVALHUB_CONTAINER_PORT: int = 8080
EVALHUB_HEALTH_PATH: str = "/api/v1/health"
EVALHUB_PROVIDERS_PATH: str = "/api/v1/evaluations/providers"
EVALHUB_HEALTH_STATUS_HEALTHY: str = "healthy"

EVALHUB_APP_LABEL: str = "eval-hub"

# CRD details
EVALHUB_API_GROUP: str = "trustyai.opendatahub.io"
EVALHUB_API_VERSION: str = "v1alpha1"
EVALHUB_KIND: str = "EvalHub"
EVALHUB_PLURAL: str = "evalhubs"
32 changes: 32 additions & 0 deletions tests/model_explainability/evalhub/test_evalhub_health.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import pytest
from ocp_resources.route import Route

from tests.model_explainability.evalhub.utils import validate_evalhub_health


@pytest.mark.parametrize(
"model_namespace",
[
pytest.param(
{"name": "test-evalhub-health"},
),
],
indirect=True,
)
@pytest.mark.smoke
@pytest.mark.model_explainability
class TestEvalHubHealth:
"""Tests for basic EvalHub service health and availability."""

def test_evalhub_health_endpoint(
self,
current_client_token: str,
evalhub_ca_bundle_file: str,
evalhub_route: Route,
) -> None:
"""Verify the EvalHub service responds with healthy status via kube-rbac-proxy."""
validate_evalhub_health(
host=evalhub_route.host,
token=current_client_token,
ca_bundle_file=evalhub_ca_bundle_file,
)
47 changes: 47 additions & 0 deletions tests/model_explainability/evalhub/utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import requests
from simple_logger.logger import get_logger

from tests.model_explainability.evalhub.constants import (
EVALHUB_HEALTH_PATH,
EVALHUB_HEALTH_STATUS_HEALTHY,
)
from utilities.guardrails import get_auth_headers

LOGGER = get_logger(name=__name__)


def validate_evalhub_health(
host: str,
token: str,
ca_bundle_file: str,
) -> None:
"""Validate that the EvalHub service health endpoint returns healthy status.

Args:
host: Route host for the EvalHub service.
token: Bearer token for authentication.
ca_bundle_file: Path to CA bundle for TLS verification.

Raises:
AssertionError: If the health check fails.
requests.HTTPError: If the request fails.
"""
url = f"https://{host}{EVALHUB_HEALTH_PATH}"
LOGGER.info(f"Checking EvalHub health at {url}")

response = requests.get(
url=url,
headers=get_auth_headers(token=token),
verify=ca_bundle_file,
timeout=10,
)
response.raise_for_status()

data = response.json()
LOGGER.info(f"EvalHub health response: {data}")

assert "status" in data, "Health response missing 'status' field"
assert data["status"] == EVALHUB_HEALTH_STATUS_HEALTHY, (
f"Expected status '{EVALHUB_HEALTH_STATUS_HEALTHY}', got '{data['status']}'"
)
assert "timestamp" in data, "Health response missing 'timestamp' field"
48 changes: 48 additions & 0 deletions utilities/resources/evalhub.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# Generated using https://github.com/RedHatQE/openshift-python-wrapper/blob/main/scripts/resource/README.md


from typing import Any

from ocp_resources.resource import NamespacedResource


class EvalHub(NamespacedResource):
"""
EvalHub is the Schema for the evalhubs API
"""

Comment thread
ruivieira marked this conversation as resolved.
api_group: str = NamespacedResource.ApiGroup.TRUSTYAI_OPENDATAHUB_IO

def __init__(
self,
env: list[Any] | None = None,
replicas: int | None = None,
**kwargs: Any,
) -> None:
r"""
Args:
env (list[Any]): Environment variables for the eval-hub container

replicas (int): Number of replicas for the eval-hub deployment

"""
super().__init__(**kwargs)

self.env = env
self.replicas = replicas

def to_dict(self) -> None:

super().to_dict()

if not self.kind_dict and not self.yaml_file:
self.res["spec"] = {}
_spec = self.res["spec"]
Comment thread
dbasunag marked this conversation as resolved.

if self.env is not None:
_spec["env"] = self.env

if self.replicas is not None:
_spec["replicas"] = self.replicas

# End of generated code