Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
30 changes: 30 additions & 0 deletions tests/model_registry/model_catalog/huggingface/conftest.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import pytest
import time
from huggingface_hub import HfApi
from simple_logger.logger import get_logger
from tests.model_registry.utils import execute_get_command

LOGGER = get_logger(name=__name__)

Expand Down Expand Up @@ -34,3 +36,31 @@ def num_models_from_hf_api_with_matching_criteria(request: pytest.FixtureRequest
else:
model_list.append(model.id)
return len(model_list)


@pytest.fixture(scope="module")
def epoch_time_before_config_map_update() -> float:
"""
Return the current epoch time in milliseconds when the test class starts.
Useful for comparing against timestamps created during test execution.
"""
return float(time.time() * 1000)
Comment thread
dbasunag marked this conversation as resolved.
Comment thread
lugi0 marked this conversation as resolved.


@pytest.fixture(scope="function")
def initial_last_synced_values(
request: pytest.FixtureRequest,
model_catalog_rest_url: list[str],
model_registry_rest_headers: dict[str, str],
) -> str:
"""
Collect initial last_synced values for a given model.
"""
model_name = request.param
url = f"{model_catalog_rest_url[0]}sources/hf_id/models/{model_name}"
result = execute_get_command(
url=url,
headers=model_registry_rest_headers,
)

return result["customProperties"]["last_synced"]["string_value"]
Comment thread
dbasunag marked this conversation as resolved.
Original file line number Diff line number Diff line change
@@ -1,23 +1,66 @@
import pytest
from typing import Self
from typing import Self, Generator
from ocp_resources.config_map import ConfigMap
from simple_logger.logger import get_logger
from tests.model_registry.model_catalog.constants import HF_MODELS
from tests.model_registry.model_catalog.constants import HF_MODELS, HF_SOURCE_ID
from tests.model_registry.model_catalog.utils import (
get_hf_catalog_str,
)
from tests.model_registry.utils import execute_get_command
from tests.model_registry.model_catalog.huggingface.utils import (
assert_huggingface_values_matches_model_catalog_api_values,
wait_for_huggingface_retrival_match,
wait_for_hugging_face_model_import,
wait_for_last_sync_update,
)
from kubernetes.dynamic import DynamicClient

LOGGER = get_logger(name=__name__)

pytestmark = [
pytest.mark.usefixtures("updated_dsc_component_state_scope_session", "model_registry_namespace"),
]

class TestLastSyncedMetadataValidation:
"""Test HuggingFace model last synced timestamp validation"""

Comment thread
dbasunag marked this conversation as resolved.
@pytest.mark.parametrize(
"updated_catalog_config_map_scope_function, initial_last_synced_values, model_name",
[
pytest.param(
"""
catalogs:
- name: HuggingFace Hub
id: hf_id
type: hf
enabled: true
includedModels:
- microsoft/phi-2
properties:
syncInterval: '2m'
""",
"microsoft/phi-2",
"microsoft/phi-2",
id="test_hf_last_synced_custom",
),
],
indirect=["updated_catalog_config_map_scope_function", "initial_last_synced_values"],
)
def test_huggingface_last_synced_custom(
self: Self,
updated_catalog_config_map_scope_function: Generator[ConfigMap, str, str],
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated
initial_last_synced_values: str,
model_catalog_rest_url: list[str],
model_registry_rest_headers: dict[str, str],
model_name: str,
Comment thread
dbasunag marked this conversation as resolved.
):
"""
Custom test for HuggingFace model last synced validation
"""
Comment thread
dbasunag marked this conversation as resolved.
# Get the model name from the parametrized test
wait_for_last_sync_update(
model_registry_rest_headers=model_registry_rest_headers,
model_catalog_rest_url=model_catalog_rest_url,
model_name=model_name,
initial_last_synced_values=float(initial_last_synced_values),
)
Comment thread
dbasunag marked this conversation as resolved.


@pytest.mark.parametrize(
Expand All @@ -34,10 +77,60 @@
],
indirect=True,
)
@pytest.mark.usefixtures("updated_catalog_config_map")
@pytest.mark.usefixtures("epoch_time_before_config_map_update", "updated_catalog_config_map")
class TestHuggingFaceModelValidation:
"""Test HuggingFace model values by comparing values between HF API calls and Model Catalog api call"""

def test_huggingface_model_metadata_last_synced(
Comment thread
lugi0 marked this conversation as resolved.
self: Self,
epoch_time_before_config_map_update: float,
updated_catalog_config_map: tuple[ConfigMap, str, str],
model_catalog_rest_url: list[str],
model_registry_rest_headers: dict[str, str],
expected_catalog_values: dict[str, str],
huggingface_api: bool,
Comment thread
dbasunag marked this conversation as resolved.
Outdated
):
"""
Validate HuggingFace model last synced timestamp is properly updated
"""
Comment thread
dbasunag marked this conversation as resolved.
LOGGER.info(
f"Validating HuggingFace model last synced timestamps with {epoch_time_before_config_map_update} "
"epoch (milliseconds)"
)
error = {}
for model_name in expected_catalog_values:
url = f"{model_catalog_rest_url[0]}sources/{HF_SOURCE_ID}/models/{model_name}"
result = execute_get_command(
url=url,
headers=model_registry_rest_headers,
)
Comment thread
dbasunag marked this conversation as resolved.
Outdated

error_msg = ""
if result["name"] != model_name:
error_msg += f"Expected model name {model_name}, but got {result['name']}. "

# Extract last_synced timestamp
last_synced = result["customProperties"]["last_synced"]["string_value"]
LOGGER.info(f"Model {model_name} last synced at: {last_synced}")

# Validate that last_synced field exists and is not empty
if last_synced is None:
error_msg += f"last_synced field is None for model {model_name}. "
elif last_synced == "":
error_msg += f"last_synced field is empty for model {model_name}. "
else:
Comment thread
dbasunag marked this conversation as resolved.
Outdated
# Compare timestamps: current_epoch_time should be earlier than last_synced
if epoch_time_before_config_map_update > float(last_synced):
Comment thread
dbasunag marked this conversation as resolved.
Outdated
Comment thread
lugi0 marked this conversation as resolved.
Outdated
error_msg += (
f"Model {model_name} last_synced ({last_synced}) should be after "
f"test start time ({epoch_time_before_config_map_update}). "
)
if error_msg:
error[model_name] = error_msg
if error:
LOGGER.error(error)
pytest.fail("Last synced validation failed")

def test_huggingface_model_metadata(
self: Self,
updated_catalog_config_map: tuple[ConfigMap, str, str],
Expand Down
36 changes: 36 additions & 0 deletions tests/model_registry/model_catalog/huggingface/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -140,3 +140,39 @@ def wait_for_hugging_face_model_import(
else:
LOGGER.warning(f"No relevant log entry found: {log}")
return False


@retry(wait_timeout=135, sleep=15)
def wait_for_last_sync_update(
model_catalog_rest_url: list[str],
model_registry_rest_headers: dict[str, str],
model_name: str,
initial_last_synced_values: float,
) -> bool:
"""Wait for the last_synced value to be updated with exact 120-second difference"""
url = f"{model_catalog_rest_url[0]}sources/hf_id/models/{model_name}"
result = execute_get_command(
url=url,
headers=model_registry_rest_headers,
)
Comment thread
dbasunag marked this conversation as resolved.
Outdated

current_last_synced = float(result["customProperties"]["last_synced"]["string_value"])
if current_last_synced != initial_last_synced_values:
# Calculate difference in milliseconds and convert to seconds
difference_seconds = int((current_last_synced - initial_last_synced_values) / 1000)

LOGGER.info(
f"Model {model_name}: initial={initial_last_synced_values}, current={current_last_synced}, "
f"diff={difference_seconds}s"
)
expected_diff = 120
if difference_seconds == expected_diff:
LOGGER.info(f"Model {model_name} successfully synced with correct interval ({difference_seconds}s)")
return True
else:
LOGGER.error(
f"Model {model_name}: sync interval should be {expected_diff}s, "
f"but found {difference_seconds}s (difference: {abs(difference_seconds - expected_diff)}s). "
Comment thread
dbasunag marked this conversation as resolved.
f"Initial: {initial_last_synced_values}, Current: {current_last_synced}"
)
return False