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
2 changes: 2 additions & 0 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -338,6 +338,8 @@ jobs:
env:
_TYPER_FORCE_DISABLE_TERMINAL: "1"
E2E_SERVICES_LOG: ${{ runner.temp }}/services.log
NGC_API_KEY: ${{ secrets.NGC_API_KEY }}
HF_TOKEN: ${{ secrets.HF_TOKEN }}
- name: Dump server logs
if: always()
run: |
Expand Down
File renamed without changes.
225 changes: 225 additions & 0 deletions e2e/files/test_storage_backends.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,225 @@
"""E2E tests for external storage backends (NGC, Hugging Face).

These tests verify that the files service can create filesets backed by
external storage providers and read files from them via the SDK.

NGC tests require ``NGC_API_KEY`` in the environment and are skipped
otherwise. Hugging Face tests use a small public repo; when ``HF_TOKEN``
is set the request is authenticated (avoids rate-limits in CI).
"""

import os
import tempfile
import uuid
from collections.abc import Iterator
from pathlib import Path

import pytest
from nemo_platform import NeMoPlatform
from nemo_platform.types.files import HuggingfaceStorageConfigParam, NGCStorageConfigParam

# ---------------------------------------------------------------------------
# NGC configuration
# ---------------------------------------------------------------------------
NGC_API_KEY_ENV = "NGC_API_KEY"

NGC_ORG = "nvidian"
NGC_TEAM = "nemo-llm"
NGC_TARGET = "nemo-platform-quickstart"
NGC_TARGET_TYPE = "resource"

# ---------------------------------------------------------------------------
# Hugging Face configuration — small public model
# ---------------------------------------------------------------------------
HF_TOKEN_ENV = "HF_TOKEN"

HF_REPO_ID = "hf-internal-testing/tiny-random-bert"
HF_REPO_TYPE = "model"

# ---------------------------------------------------------------------------
# Fixtures
# ---------------------------------------------------------------------------


@pytest.fixture
def ngc_api_key() -> str:
"""Return the NGC API key from the environment."""
key = os.environ.get(NGC_API_KEY_ENV)
assert key, f"{NGC_API_KEY_ENV} must be set"
return key


@pytest.fixture
def ngc_secret(sdk: NeMoPlatform, workspace: str, ngc_api_key: str) -> Iterator[str]:
"""Create a secret containing the NGC API key, cleaned up after test."""
secret_name = f"e2e-ngc-key-{uuid.uuid4().hex[:8]}"
sdk.secrets.create(workspace=workspace, name=secret_name, value=ngc_api_key)
yield secret_name
try:
sdk.secrets.delete(workspace=workspace, name=secret_name)
except Exception:
Comment thread
github-advanced-security[bot] marked this conversation as resolved.
Fixed
pass


@pytest.fixture
def ngc_fileset(sdk: NeMoPlatform, workspace: str, ngc_secret: str) -> Iterator[str]:
"""Create an NGC-backed fileset, cleaned up after test."""
fileset_name = f"e2e-ngc-fs-{uuid.uuid4().hex[:8]}"
sdk.files.filesets.create(
workspace=workspace,
name=fileset_name,
description="E2E test NGC-backed fileset",
storage=NGCStorageConfigParam(
api_key_secret=ngc_secret,
org=NGC_ORG,
team=NGC_TEAM,
target=NGC_TARGET,
target_type=NGC_TARGET_TYPE,
),
)
yield fileset_name
try:
sdk.files.filesets.delete(fileset_name, workspace=workspace)
except Exception:
Comment thread
github-advanced-security[bot] marked this conversation as resolved.
Fixed
pass


@pytest.fixture
def hf_secret(sdk: NeMoPlatform, workspace: str) -> Iterator[str | None]:
Comment thread
matthewgrossman marked this conversation as resolved.
Outdated
"""Create a secret for the HF token if available, otherwise yield None."""
token = os.environ.get(HF_TOKEN_ENV)
if not token:
yield None
return

secret_name = f"e2e-hf-tok-{uuid.uuid4().hex[:8]}"
sdk.secrets.create(workspace=workspace, name=secret_name, value=token)
yield secret_name
try:
sdk.secrets.delete(workspace=workspace, name=secret_name)
except Exception:
Comment thread
github-advanced-security[bot] marked this conversation as resolved.
Fixed
pass


@pytest.fixture
def hf_fileset(sdk: NeMoPlatform, workspace: str, hf_secret: str | None) -> Iterator[str]:
"""Create a Hugging Face-backed fileset, cleaned up after test."""
fileset_name = f"e2e-hf-fs-{uuid.uuid4().hex[:8]}"

storage = HuggingfaceStorageConfigParam(
repo_id=HF_REPO_ID,
repo_type=HF_REPO_TYPE,
)
if hf_secret is not None:
storage["token_secret"] = hf_secret

sdk.files.filesets.create(
workspace=workspace,
name=fileset_name,
description="E2E test HF-backed fileset",
storage=storage,
)
yield fileset_name
try:
sdk.files.filesets.delete(fileset_name, workspace=workspace)
except Exception:
Comment thread
github-advanced-security[bot] marked this conversation as resolved.
Fixed
pass


# ===================================================================
# NGC tests
# ===================================================================


@pytest.mark.skipif(not os.environ.get(NGC_API_KEY_ENV), reason=f"{NGC_API_KEY_ENV} not set")
class TestNGCFileset:
"""Tests for NGC-backed filesets."""

def test_list_files(self, sdk: NeMoPlatform, workspace: str, ngc_fileset: str):
"""Listing an NGC-backed fileset returns files with paths and sizes."""
files = sdk.files.list(fileset=ngc_fileset, workspace=workspace)
assert len(files.data) > 0, "NGC fileset should contain at least one file"

for f in files.data:
assert f.path, "Each file should have a path"
assert f.size > 0, "Each file should have a non-zero size"

def test_download_file(self, sdk: NeMoPlatform, workspace: str, ngc_fileset: str):
"""Downloading the smallest file from an NGC fileset succeeds and size matches."""
files = sdk.files.list(fileset=ngc_fileset, workspace=workspace)
assert len(files.data) > 0

target = min(files.data, key=lambda f: f.size)

with tempfile.TemporaryDirectory() as tmpdir:
local_path = Path(tmpdir) / target.path.replace("/", "_")
sdk.files.download(
fileset=ngc_fileset,
workspace=workspace,
remote_path=target.path,
local_path=str(local_path),
)
assert local_path.exists()
assert local_path.stat().st_size == target.size

def test_cache_status(self, sdk: NeMoPlatform, workspace: str, ngc_fileset: str):
"""NGC-backed files report a cacheable status."""
files = sdk.files.list(
fileset=ngc_fileset,
workspace=workspace,
include_cache_status=True,
)
assert len(files.data) > 0

for f in files.data:
assert f.cache_status is not None
assert f.cache_status != "not_cacheable"


# ===================================================================
# Hugging Face tests
# ===================================================================


class TestHuggingFaceFileset:
"""Tests for Hugging Face-backed filesets."""

def test_list_files(self, sdk: NeMoPlatform, workspace: str, hf_fileset: str):
"""Listing an HF-backed fileset returns files with paths and sizes."""
files = sdk.files.list(fileset=hf_fileset, workspace=workspace)
assert len(files.data) > 0, "HF fileset should contain at least one file"

for f in files.data:
assert f.path, "Each file should have a path"
assert f.size > 0, "Each file should have a non-zero size"

def test_download_file(self, sdk: NeMoPlatform, workspace: str, hf_fileset: str):
"""Downloading the smallest file from an HF fileset succeeds and size matches."""
files = sdk.files.list(fileset=hf_fileset, workspace=workspace)
assert len(files.data) > 0

target = min(files.data, key=lambda f: f.size)

with tempfile.TemporaryDirectory() as tmpdir:
local_path = Path(tmpdir) / target.path.replace("/", "_")
sdk.files.download(
fileset=hf_fileset,
workspace=workspace,
remote_path=target.path,
local_path=str(local_path),
)
assert local_path.exists()
assert local_path.stat().st_size == target.size

def test_cache_status(self, sdk: NeMoPlatform, workspace: str, hf_fileset: str):
"""HF-backed files report a cacheable status."""
files = sdk.files.list(
fileset=hf_fileset,
workspace=workspace,
include_cache_status=True,
)
assert len(files.data) > 0

for f in files.data:
assert f.cache_status is not None
assert f.cache_status != "not_cacheable"
Loading