Skip to content

Commit 382faa5

Browse files
authored
Merge branch 'main' into add_providerlist
2 parents a6c203f + b28f0ed commit 382faa5

File tree

16 files changed

+652
-41
lines changed

16 files changed

+652
-41
lines changed

CONSTITUTION.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ All code MUST consider security implications.
7979
- Avoid running destructive commands without explicit user confirmation
8080
- Use detect-secrets and gitleaks pre-commit hooks to prevent secret leakage
8181
- Test code MUST NOT introduce vulnerabilities into the tested systems
82+
- Use `utilities.path_utils.resolve_repo_path` to resolve and validate any user-supplied or parameterized file paths, preventing path-traversal and symlink-escape outside the repository root
8283
- JIRA ticket links are allowed in PRs and commit messages (our Jira is public)
8384
- Do NOT reference internal-only resources (Jenkins, Confluence, Slack threads) in code, PRs, or commit messages
8485
- Do NOT link embargoed or security-restricted (RH-employee-only) tickets

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ output-format = "grouped"
66
extend-exclude = ["utilities/manifests"]
77

88
[tool.ruff.lint]
9-
external = ["E501"]
9+
external = ["E501", "FCN001"]
1010

1111
[tool.ruff.format]
1212
exclude = [".git", ".venv", ".mypy_cache", ".tox", "__pycache__", "utilities/manifests"]

pytest.ini

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,6 @@ testpaths = tests
44

55
markers =
66
# General
7-
polarion: Store polarion test ID
8-
97
skip_on_disconnected: Mark tests that can only be run in deployments with Internet access i.e. not on disconnected clusters.
108
parallel: marks tests that can run in parallel along with pytest-xdist
119

tests/cluster_health/README.md

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
# Cluster Health Tests
2+
3+
This directory contains foundational health check tests for OpenDataHub/RHOAI clusters. These tests serve as prerequisites to ensure the cluster and operators are in a healthy state before running more complex integration tests.
4+
5+
## Directory Structure
6+
7+
```text
8+
cluster_health/
9+
├── test_cluster_health.py # Cluster node health validation
10+
└── test_operator_health.py # Operator and pod health validation
11+
```
12+
13+
### Current Test Suites
14+
15+
- **`test_cluster_health.py`** - Validates that all cluster nodes are healthy and schedulable
16+
- **`test_operator_health.py`** - Validates that DSCInitialization, DataScienceCluster resources are ready, and all pods in operator/application namespaces are running
17+
18+
## Test Markers
19+
20+
Tests use the following markers defined in `pytest.ini`:
21+
22+
- `@pytest.mark.cluster_health` - Tests that verify the cluster is healthy to begin testing
23+
- `@pytest.mark.operator_health` - Tests that verify OpenDataHub/RHOAI operators are healthy and functioning correctly
24+
25+
## Test Details
26+
27+
### Cluster Node Health (`test_cluster_health.py`)
28+
29+
- **`test_cluster_node_healthy`** - Asserts all cluster nodes have `KubeletReady: True` condition and are schedulable (not cordoned)
30+
31+
### Operator Health (`test_operator_health.py`)
32+
33+
- **`test_data_science_cluster_initialization_healthy`** - Validates the DSCInitialization resource reaches `READY` status (120s timeout)
34+
- **`test_data_science_cluster_healthy`** - Validates the DataScienceCluster resource reaches `READY` status (120s timeout)
35+
- **`test_pods_cluster_healthy`** - Validates all pods in operator and application namespaces reach Running/Completed state (180s timeout). Parametrized across `operator_namespace` and `applications_namespace` from global config
36+
37+
## Running Tests
38+
39+
### Run All Cluster Health Tests
40+
41+
```bash
42+
uv run pytest tests/cluster_health/
43+
```
44+
45+
### Run by Marker
46+
47+
```bash
48+
# Run cluster node health tests
49+
uv run pytest -m cluster_health
50+
51+
# Run operator health tests
52+
uv run pytest -m operator_health
53+
54+
# Run both
55+
uv run pytest -m "cluster_health or operator_health"
56+
```

tests/fixtures/README.md

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
# Shared Test Fixtures
2+
3+
This directory contains shared pytest fixtures that are used across multiple test modules. These fixtures are automatically loaded via pytest's plugin mechanism, registered in `/tests/conftest.py`.
4+
5+
## Directory Structure
6+
7+
```text
8+
fixtures/
9+
├── files.py # File storage provider fixtures
10+
├── guardrails.py # Guardrails orchestrator infrastructure fixtures
11+
├── inference.py # Inference service and serving runtime fixtures
12+
├── trustyai.py # TrustyAI operator and DSC configuration fixtures
13+
└── vector_io.py # Vector database provider deployment fixtures
14+
```
15+
16+
### Fixture Modules
17+
18+
- **`files.py`** - Factory fixture for configuring file storage providers (local, S3/MinIO)
19+
- **`guardrails.py`** - Fixtures for deploying and configuring the Guardrails Orchestrator, including pods, routes, health checks, and gateway configuration
20+
- **`inference.py`** - Fixtures for vLLM CPU serving runtimes, InferenceServices (Qwen), LLM-d inference simulator, and KServe controller configuration
21+
- **`trustyai.py`** - Fixtures for TrustyAI operator deployment and DataScienceCluster LMEval configuration
22+
- **`vector_io.py`** - Factory fixture for deploying vector database providers (Milvus, Faiss, PGVector, Qdrant) with their backing services and configuration
23+
24+
## Registration
25+
26+
All fixture modules are registered as pytest plugins in `/tests/conftest.py`:
27+
28+
```python
29+
pytest_plugins = [
30+
"tests.fixtures.inference",
31+
"tests.fixtures.guardrails",
32+
"tests.fixtures.trustyai",
33+
"tests.fixtures.vector_io",
34+
"tests.fixtures.files",
35+
]
36+
```
37+
38+
## Usage
39+
40+
Fixtures are automatically available to all tests. Factory fixtures accept parameters via `pytest.mark.parametrize` with `indirect=True`.
41+
42+
### Vector I/O Provider Example
43+
44+
```python
45+
@pytest.mark.parametrize(
46+
"vector_io_provider_deployment_config_factory",
47+
["milvus", "pgvector", "qdrant-remote"],
48+
indirect=True,
49+
)
50+
def test_with_vector_db(vector_io_provider_deployment_config_factory):
51+
# Fixture deploys the provider and returns env var configuration
52+
...
53+
```
54+
55+
### Supported Vector I/O Providers
56+
57+
| Provider | Type | Description |
58+
| --------------- | ------ | ------------------------------------------- |
59+
| `milvus` | Local | In-memory Milvus (no external dependencies) |
60+
| `milvus-remote` | Remote | Milvus standalone with etcd backend |
61+
| `faiss` | Local | Facebook AI Similarity Search (in-memory) |
62+
| `pgvector` | Local | PostgreSQL with pgvector extension |
63+
| `qdrant-remote` | Remote | Qdrant vector database |
64+
65+
### Supported File Providers
66+
67+
| Provider | Description |
68+
| -------- | ---------------------------------- |
69+
| `local` | Local filesystem storage (default) |
70+
| `s3` | S3/MinIO remote object storage |
71+
72+
## Adding New Fixtures
73+
74+
When adding shared fixtures, place them in the appropriate module file (or create a new one), and register the new module in `/tests/conftest.py` under `pytest_plugins`. Follow the project's fixture conventions: use noun-based names, narrowest appropriate scope, and context managers for resource lifecycle.

tests/llama_stack/README.md

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -88,23 +88,23 @@ LLS_FILES_S3_AUTO_CREATE_BUCKET=true # Optional
8888
To run all tests in the `/tests/llama_stack` directory:
8989

9090
```bash
91-
pytest tests/llama_stack/
91+
uv run pytest tests/llama_stack/
9292
```
9393

9494
### Run Tests by Component/Team
9595

9696
To run tests for a specific team (e.g. rag):
9797

9898
```bash
99-
pytest -m rag tests/llama_stack/
99+
uv run pytest -m rag tests/llama_stack/
100100
```
101101

102102
### Run Tests for a llama-stack API
103103

104104
To run tests for a specific API (e.g., vector_io):
105105

106106
```bash
107-
pytest tests/llama_stack/vector_io
107+
uv run pytest tests/llama_stack/vector_io
108108
```
109109

110110
### Run Tests with Additional Markers
@@ -113,10 +113,10 @@ You can combine team markers with other pytest markers:
113113

114114
```bash
115115
# Run only smoke tests for rag
116-
pytest -m "rag and smoke" tests/llama_stack/
116+
uv run pytest -m "rag and smoke" tests/llama_stack/
117117

118118
# Run all rag tests except the ones requiring a GPU
119-
pytest -m "rag and not gpu" tests/llama_stack/
119+
uv run pytest -m "rag and not gpu" tests/llama_stack/
120120
```
121121

122122
## Related Testing Repositories
@@ -145,5 +145,3 @@ For information about the APIs and Providers available in the Red Hat LlamaStack
145145
## Additional Resources
146146

147147
- [Llama Stack Documentation](https://llamastack.github.io/docs/)
148-
- [OpenDataHub Documentation](https://opendatahub.io/docs)
149-
- [OpenShift AI Documentation](https://docs.redhat.com/en/documentation/red_hat_openshift_ai_self-managed)

tests/llama_stack/conftest.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import os
22
from collections.abc import Callable, Generator
3-
from pathlib import Path
43
from typing import Any
54

65
import httpx
@@ -810,7 +809,6 @@ def vector_store(
810809
try:
811810
vector_store_upload_doc_sources(
812811
doc_sources=doc_sources,
813-
repo_root=Path(request.config.rootdir).resolve(),
814812
llama_stack_client=unprivileged_llama_stack_client,
815813
vector_store=vector_store,
816814
vector_io_provider=vector_io_provider,

tests/llama_stack/utils.py

Lines changed: 7 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
LLS_CORE_POD_FILTER,
2121
)
2222
from utilities.exceptions import UnexpectedResourceCountError
23+
from utilities.path_utils import resolve_repo_path
2324
from utilities.resources.llama_stack_distribution import LlamaStackDistribution
2425

2526
LOGGER = get_logger(name=__name__)
@@ -279,38 +280,32 @@ def vector_store_create_file_from_path(
279280

280281

281282
def vector_store_upload_doc_sources(
282-
doc_sources: Any,
283-
repo_root: Path,
283+
doc_sources: list[str],
284284
llama_stack_client: LlamaStackClient,
285285
vector_store: Any,
286286
vector_io_provider: str,
287287
) -> None:
288288
"""Upload parametrized document sources (URLs and repo-local paths) to a vector store.
289289
290-
Resolves each local path under ``repo_root`` and re-resolves directory entries to avoid
291-
symlink escape outside the repository.
290+
Resolves each local path via ``resolve_repo_path`` and re-resolves directory entries
291+
to avoid symlink escape outside the repository.
292292
293293
Args:
294294
doc_sources: List of URL or path strings (repo-relative or absolute under repo root).
295-
repo_root: Resolved repository root; local paths must resolve under this directory.
296295
llama_stack_client: Client used for file and vector store APIs.
297296
vector_store: Target vector store (must expose ``id``).
298297
vector_io_provider: Provider id for log context only.
299298
300299
Raises:
301-
TypeError: If ``doc_sources`` is not a list.
302-
ValueError: If a local path resolves outside ``repo_root``.
300+
ValueError: If a local path resolves outside the repo root.
303301
FileNotFoundError: If a file or non-empty directory source is missing.
304302
"""
305-
if not isinstance(doc_sources, list):
306-
raise TypeError(f"doc_sources must be a list[str], got {type(doc_sources).__name__}")
307303
LOGGER.info(
308304
"Uploading doc_sources to vector_store (provider_id=%s, id=%s): %s",
309305
vector_io_provider,
310306
vector_store.id,
311307
doc_sources,
312308
)
313-
repo_root_resolved = repo_root.resolve()
314309
for source in doc_sources:
315310
if source.startswith(("http://", "https://")):
316311
vector_store_create_file_from_url(
@@ -319,25 +314,14 @@ def vector_store_upload_doc_sources(
319314
vector_store=vector_store,
320315
)
321316
continue
322-
raw_path = Path(source) # noqa: FCN001
323-
resolved_source = raw_path.resolve() if raw_path.is_absolute() else (repo_root_resolved / raw_path).resolve()
324-
if not resolved_source.is_relative_to(repo_root_resolved):
325-
raise ValueError(
326-
f"doc_sources path must be under repo root ({repo_root_resolved}): {source!r}",
327-
)
328-
source_path = resolved_source
317+
source_path = resolve_repo_path(source=source)
329318

330319
if source_path.is_dir():
331320
files = sorted(source_path.iterdir())
332321
if not files:
333322
raise FileNotFoundError(f"No files found in directory: {source_path}")
334323
for file_path in files:
335-
file_path_resolved = file_path.resolve(strict=True)
336-
if not file_path_resolved.is_relative_to(repo_root_resolved):
337-
raise ValueError(
338-
f"doc_sources directory entry must resolve under repo root "
339-
f"({repo_root_resolved}): {file_path!r} -> {file_path_resolved!r}",
340-
)
324+
file_path_resolved = resolve_repo_path(source=file_path)
341325
if not file_path_resolved.is_file():
342326
continue
343327
vector_store_create_file_from_path(

0 commit comments

Comments
 (0)