Skip to content

Commit 6675ec1

Browse files
authored
feat: Workspace environment persisted in the network volume (#10)
* chore: make clean and make upgrade * ci: make test|test-unit|test-integration|test-coverage|test-fast * ci: make lint|lint-fix|format|format-check * build: updated dependencies to latest * chore: update tetra-rp * ci: simplified CI workflow + manual deploy action * test: sample unit and integration tests Coverage fails when at below 50%. We can increase this later. * chore: make lint-fix * chore: make dev as default when running `make` * chore: `make` shows help list of all available commands * chore: forgot to include python 3.10 in the tests * chore: ruff should exclude tetra-rp sub-module * ci: test coverage fails at <35% (temp) * test: auto-mark unit and integration tests appropriately * ci: make typecheck with mypy as quality-check * chore: reset uv.lock * test: add comprehensive test foundation for volume workspace TDD Establish testing infrastructure and protocol validation tests. Create shared fixtures and validates the FunctionRequest/FunctionResponse data models that will be extended for volume workspace functionality. * test: volume detection and workspace initialization Tests volume detection, virtual environment creation, file-based locking for concurrency, and timeout handling mechanisms. * test: volume-aware execution context Validates that functions execute in volume workspace, can access persistent packages, and fallback gracefully when volume is unavailable. * test: integration tests for end-to-end workflows Tests complete request workflows, concurrent access safety, mixed execution scenarios, and realistic error handling patterns. * feat: implement core volume workspace detection and initialization Adds volume detection logic, workspace initialization with file-based locking, virtual environment creation, and timeout handling to make some tests pass. * feat: add differential dependency management for volume persistence Implement smart dependency installation that only installs missing packages. Optimizes performance by leveraging persistent volume storage and avoiding redundant package installations. * feat: implement volume-aware execution context and environment configuration Enable functions to execute in volume workspace with access to persistent packages. Configures Python path, environment variables, and UV cache to utilize volume storage effectively. * test: enhance existing test suites for volume workspace compatibility Update existing tests to work with new volume workspace functionality. Ensures backward compatibility and validates that all existing functionality continues to work with the new volume-aware implementation. * build: make update + cleanup * chore: update tetra-rp submodule * chore: updated CLAUDE.md * chore: update CLAUDE.md * chore: break out the large handler into modules also their tests * refactor: constants for the magic values * refactor: move Python files to src/ directory - Move all Python modules to src/ for better organization - Update Docker files to copy from src/ directory - Update pyproject.toml with src/ in pythonpath - Update Makefile to copy remote_execution.py to src/ - All tests pass with new structure * feat: add virtual environment validation and repair - Add _validate_virtual_environment() method to WorkspaceManager with symlink chain resolution using os.path.realpath() - Add _remove_broken_virtual_environment() cleanup method - Enhance initialize_workspace() with validation checks and automatic repair - Add validation calls in setup_python_path() and dependency installer - Update Docker files to work with src/ directory structure - Update tests to mock new validation methods - Fix pyproject.toml pythonpath configuration for tests This resolves broken virtual environment symlinks when different endpoints create venvs with different Python interpreter paths on shared volumes. * feat: implement per-endpoint workspace isolation for shared volumes - Add RUNPOD_ENDPOINT_ID environment variable support for endpoint isolation - Workspace paths now: /runpod-volume/runtimes/{endpoint_id} - Shared UV cache at volume root: /runpod-volume/.uv-cache - Add RUNTIMES_DIR_NAME constant for endpoint workspace organization - Update WorkspaceManager to create endpoint-specific workspace paths - Add comprehensive endpoint isolation tests - Update integration tests for new workspace structure - Resolve merge conflicts from virtual environment validation features * feat: configure Hugging Face cache to use persistent volume storage - Add HF_CACHE_DIR_NAME constant for .hf-cache directory - Implement _configure_huggingface_cache() method in WorkspaceManager - Set HF environment variables (HF_HOME, TRANSFORMERS_CACHE, etc.) to use volume paths - Update unit and integration tests to mock os.makedirs calls - Fix "No space left on device" errors when downloading HF models * fix: Test local handler execution should test all test_*.json * feat: add comprehensive handler testing with make test-handler command - Add make test-handler command that tests all test_*.json files locally - Update CI to use make test-handler for consistency between local and CI testing - Ensure local development environment matches CI testing exactly - Remove code duplication between Makefile and CI configuration - Support cross-platform testing (handles timeout command availability) - Update CLAUDE.md documentation with new testing commands * feat: enhance concurrency safety implementation for workspace initialization - Add configurable timeout constants (WORKSPACE_INIT_TIMEOUT, WORKSPACE_LOCK_POLL_INTERVAL) - Implement atomic lock file operations with proper file descriptor management - Enhance lock file cleanup with comprehensive error handling - Add workspace directory validation before lock acquisition - Fix race condition in workspace functionality checks by making them atomic - Add comprehensive timeout and edge case tests for concurrency scenarios - Improve error messages and fallback behavior for various failure modes - Maintain backward compatibility while significantly improving reliability * Revert "feat: enhance concurrency safety implementation for workspace initialization" This reverts commit a411bfe. * refactor: introduce BaseExecutor base class for executor inheritance - Add BaseExecutor abstract base class with common functionality - Update FunctionExecutor and ClassExecutor to inherit from BaseExecutor - Standardize execution environment setup via _setup_execution_environment - Update ClassExecutor constructor to accept workspace_manager parameter - Fix ClassExecutor tests to mock workspace_manager dependency * refactor: replace print statements with structured logging - Add logging support to DependencyInstaller and WorkspaceManager - Replace print calls with appropriate log levels (info, warning, error) - Improve debugging and monitoring capabilities * chore: update submodule and dependencies * fix: vLLM ignored the volume runtime The fix will resolve the vLLM subprocess errors encountered while maintaining full compatibility with existing functionality. When deployed to RunPod with volumes, libraries like vLLM that hardcode /app/.venv paths will seamlessly use the volume's virtual environment. * fix: resolve vLLM subprocess failures with volume virtual environments - Add symlink from /app/.venv to volume venv to handle hardcoded paths - Configure PYTHONPATH environment variable for subprocess compatibility - Ensure libraries like vLLM can spawn subprocesses that find installed packages - Add comprehensive test coverage for symlink functionality - Maintain backward compatibility when no volume is present
1 parent d654812 commit 6675ec1

37 files changed

Lines changed: 4771 additions & 762 deletions

.dockerignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,3 +17,4 @@ build/
1717
*.egg-info/
1818
.eggs/
1919
*.egg
20+
*.pkl

.github/workflows/ci.yml

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -100,16 +100,12 @@ jobs:
100100

101101
- name: Test local handler execution
102102
run: |
103-
timeout 30s uv run python handler.py || exit_code=$?
103+
timeout 30s make test-handler || exit_code=$?
104104
if [ ${exit_code:-0} -eq 124 ]; then
105-
echo "Handler execution timed out as expected (running in serverless mode)"
106-
exit 0
107-
elif [ ${exit_code:-0} -eq 0 ]; then
108-
echo "Handler executed successfully"
109-
exit 0
105+
echo "Handler testing timed out"
106+
exit 1
110107
else
111-
echo "Handler execution failed with exit code $exit_code"
112-
exit $exit_code
108+
exit ${exit_code:-0}
113109
fi
114110
115111
release:

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,3 +159,4 @@ cython_debug/
159159
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
160160
#.idea/
161161
remote_execution.py
162+
*.pkl

CLAUDE.md

Lines changed: 134 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -4,18 +4,20 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
44

55
## Project Overview
66

7-
This is `worker-tetra`, a RunPod Serverless worker template that provides dynamic GPU provisioning for ML workloads with transparent execution. The project consists of two main components:
7+
This is `worker-tetra`, a RunPod Serverless worker template that provides dynamic GPU provisioning for ML workloads with transparent execution and persistent workspace management. The project consists of two main components:
88

9-
1. **RunPod Worker Handler** (`handler.py`) - A serverless function that executes remote Python functions with dependency management
9+
1. **RunPod Worker Handler** (`handler.py`) - A serverless function that executes remote Python functions with dependency management and persistent volume workspace support
1010
2. **Tetra SDK** (`tetra-rp/` submodule) - Python library for distributed inference and serving of ML models
1111

1212
## Architecture
1313

1414
### Core Components
1515

1616
- **`handler.py`**: Main RunPod serverless handler implementing `RemoteExecutor` class
17-
- Executes arbitrary Python functions remotely
18-
- Handles dynamic installation of Python and system dependencies
17+
- Executes arbitrary Python functions remotely with persistent workspace support
18+
- Handles dynamic installation of Python and system dependencies with differential updates
19+
- Manages `/runpod-volume` workspace with virtual environment and shared package cache
20+
- Implements concurrency-safe workspace initialization with file-based locking
1921
- Serializes/deserializes function arguments and results using cloudpickle
2022
- Captures stdout, stderr, and logs from remote execution
2123

@@ -32,16 +34,29 @@ This is `worker-tetra`, a RunPod Serverless worker template that provides dynami
3234
### Key Patterns
3335

3436
1. **Remote Function Execution**: Functions decorated with `@remote` are automatically executed on RunPod GPU workers
35-
2. **Dynamic Dependency Management**: Dependencies specified in decorators are installed at runtime
36-
3. **Serialization**: Uses cloudpickle + base64 encoding for function arguments and results
37-
4. **Resource Configuration**: `LiveServerless` objects define GPU requirements, scaling, and worker configuration
37+
2. **Persistent Workspace Management**: `/runpod-volume` provides persistent storage for packages and execution state
38+
3. **Dynamic Dependency Management**: Dependencies specified in decorators are installed at runtime with differential updates
39+
4. **Concurrency Safety**: File-based locking ensures safe workspace initialization across multiple workers
40+
5. **Serialization**: Uses cloudpickle + base64 encoding for function arguments and results
41+
6. **Resource Configuration**: `LiveServerless` objects define GPU requirements, scaling, and worker configuration
3842

3943
## Development Commands
4044

4145
### Setup and Dependencies
4246
```bash
4347
make setup # Initialize project, sync dependencies, update submodules
44-
make dev # Install all development dependencies
48+
make dev # Install all development dependencies (includes pytest, ruff)
49+
uv sync # Sync production dependencies only
50+
uv sync --all-groups # Sync all dependency groups (same as make dev)
51+
```
52+
53+
### Code Quality
54+
```bash
55+
make lint # Check code with ruff linter
56+
make lint-fix # Auto-fix linting issues
57+
make format # Format code with ruff
58+
make format-check # Check if code is properly formatted
59+
make quality-check # Run all quality checks (format, lint, test coverage)
4560
```
4661

4762
### Docker Operations
@@ -53,19 +68,74 @@ make build-cpu # Build CPU-only Docker image
5368

5469
### Local Testing
5570
```bash
56-
python handler.py # Test handler locally with test_input.json
71+
# Test handler locally with test_input.json
72+
PYTHONPATH=src RUNPOD_TEST_INPUT="$(cat test_input.json)" uv run python src/handler.py
73+
74+
# Test with other test files
75+
PYTHONPATH=src RUNPOD_TEST_INPUT="$(cat test_class_input.json)" uv run python src/handler.py
76+
PYTHONPATH=src RUNPOD_TEST_INPUT="$(cat test_hf_input.json)" uv run python src/handler.py
5777
```
5878

5979
### Submodule Management
6080
```bash
6181
git submodule update --remote --merge # Update tetra-rp to latest
6282
```
6383

84+
## RunPod Volume Workspace
85+
86+
The handler automatically detects and utilizes `/runpod-volume` for persistent workspace management when available:
87+
88+
### Volume Features
89+
- **Automatic Detection**: Detects `/runpod-volume` presence on container startup
90+
- **Endpoint Isolation**: Each endpoint gets its own workspace at `/runpod-volume/runtimes/{endpoint_id}`
91+
- **Virtual Environment**: Creates and manages endpoint-specific `.venv` for persistent package installation
92+
- **Shared Package Cache**: Uses `/runpod-volume/.uv-cache` for efficient package caching across all endpoints
93+
- **Hugging Face Cache**: Configures HF model cache at `/runpod-volume/.hf-cache` to prevent storage issues
94+
- **Differential Installation**: Only installs missing packages, leveraging persistent storage
95+
- **Concurrency Safety**: File-based locking prevents race conditions during workspace initialization
96+
- **Graceful Fallback**: Works normally when no volume is present
97+
98+
### Volume Structure
99+
```
100+
/runpod-volume/
101+
├── .uv-cache/ # Shared UV package cache (across all endpoints)
102+
├── .hf-cache/ # Shared Hugging Face model cache (across all endpoints)
103+
│ ├── transformers/ # Transformers model cache
104+
│ ├── datasets/ # HF datasets cache
105+
│ └── hub/ # Hugging Face Hub cache
106+
├── runtimes/ # Per-endpoint runtime environments
107+
│ ├── endpoint-1/ # Workspace for endpoint-1
108+
│ │ ├── .venv/ # Endpoint-specific virtual environment
109+
│ │ ├── .initialization.lock # Temporary workspace lock file
110+
│ │ └── <execution workspace>
111+
│ └── endpoint-2/ # Workspace for endpoint-2
112+
│ ├── .venv/ # Endpoint-specific virtual environment
113+
│ ├── .initialization.lock
114+
│ └── <execution workspace>
115+
```
116+
117+
### Performance Benefits
118+
- **Faster Cold Starts**: Pre-installed packages and cached models reduce initialization time
119+
- **Reduced Network Usage**: Cached packages and models avoid redundant downloads
120+
- **Persistent State**: Function execution workspace survives across calls
121+
- **Endpoint Isolation**: Each endpoint maintains independent dependencies and state
122+
- **Optimized Resource Usage**: Shared caches across multiple endpoints while maintaining isolation
123+
- **ML Model Efficiency**: Large HF models cached on volume prevent "No space left on device" errors
124+
64125
## Configuration
65126

66127
### Environment Variables
67128
- `RUNPOD_API_KEY`: Required for RunPod Serverless integration
129+
- `RUNPOD_ENDPOINT_ID`: Used for workspace isolation (automatically set by RunPod)
68130
- `DEBIAN_FRONTEND=noninteractive`: Set during system package installation
131+
- `UV_CACHE_DIR`: Automatically set to `/runpod-volume/.uv-cache` when volume detected
132+
- `VIRTUAL_ENV`: Automatically set to `/runpod-volume/runtimes/{endpoint_id}/.venv` when available
133+
134+
#### Hugging Face Cache Configuration (Auto-configured when volume available)
135+
- `HF_HOME`: Set to `/runpod-volume/.hf-cache` for main HF cache directory
136+
- `TRANSFORMERS_CACHE`: Set to `/runpod-volume/.hf-cache/transformers` for model cache
137+
- `HF_DATASETS_CACHE`: Set to `/runpod-volume/.hf-cache/datasets` for dataset cache
138+
- `HUGGINGFACE_HUB_CACHE`: Set to `/runpod-volume/.hf-cache/hub` for hub cache
69139

70140
### Resource Configuration
71141
Configure GPU resources using `LiveServerless` objects:
@@ -82,18 +152,40 @@ gpu_config = LiveServerless(
82152

83153
## Testing and Quality
84154

85-
- No formal test suite currently exists
86-
- Testing is done via `test_input.json` with local handler execution
87-
- Uses `uv` for fast dependency management
88-
- Multi-stage Docker builds to minimize image size
155+
### Testing Commands
156+
```bash
157+
make test # Run all tests
158+
make test-unit # Run unit tests only
159+
make test-integration # Run integration tests only
160+
make test-coverage # Run tests with coverage report
161+
make test-fast # Run tests with fail-fast mode
162+
make test-handler # Test handler locally with all test_*.json files (same as CI)
163+
164+
# Test handler locally with specific test files
165+
PYTHONPATH=src RUNPOD_TEST_INPUT="$(cat test_input.json)" uv run python src/handler.py
166+
PYTHONPATH=src RUNPOD_TEST_INPUT="$(cat test_class_input.json)" uv run python src/handler.py
167+
PYTHONPATH=src RUNPOD_TEST_INPUT="$(cat test_hf_input.json)" uv run python src/handler.py
168+
```
169+
170+
### Testing Framework
171+
- **pytest** with coverage reporting and async support
172+
- **Unit tests** (`tests/unit/`): Test individual components in isolation
173+
- **Integration tests** (`tests/integration/`): Test end-to-end workflows
174+
- **Coverage target**: 80% minimum, with HTML and XML reports
175+
- **Test fixtures**: Shared test data and mocks in `tests/conftest.py`
176+
- **CI Integration**: Tests run on all PRs and before releases/deployments
89177

90178
## Development Notes
91179

92180
### Dependency Management
93181
- Root project uses `uv` with `pyproject.toml`
94182
- Tetra SDK has separate `pyproject.toml` in `tetra-rp/`
95183
- System dependencies installed via `apt-get` in containerized environment
96-
- Python dependencies installed via `uv pip install` at runtime
184+
- Python dependencies installed via `uv pip install` at runtime with volume persistence
185+
- **Differential Installation**: Only installs packages missing from persistent volume
186+
- **Shared Cache**: UV cache in `/runpod-volume/.uv-cache` optimizes package downloads
187+
- **Virtual Environment**: Persistent `.venv` in volume survives across function calls
188+
- **ML Model Cache**: Hugging Face models cached in `/runpod-volume/.hf-cache` prevent storage issues
97189

98190
### Error Handling
99191
- All remote execution wrapped in try/catch with full traceback capture
@@ -103,17 +195,31 @@ gpu_config = LiveServerless(
103195
### Security Considerations
104196
- Functions execute arbitrary Python code in sandboxed containers
105197
- System package installation requires root privileges in container
198+
- Volume workspace provides persistent storage but maintains container isolation
199+
- File-based locking prevents race conditions during concurrent workspace access
106200
- No secrets should be committed to repository
107201
- API keys passed via environment variables
108202

109203
## File Structure Highlights
110204

111205
```
112-
├── handler.py # Main serverless function handler
206+
├── handler.py # Main serverless function handler with volume support
113207
├── remote_execution.py # Protocol definitions
208+
├── PLAN.md # TDD implementation plan for volume workspace
114209
├── Dockerfile # GPU container definition
115210
├── Dockerfile-cpu # CPU container definition
116-
├── test_input.json # Sample input for local testing
211+
├── test_input.json # Basic function execution test
212+
├── test_class_input.json # Class execution test
213+
├── test_hf_input.json # HuggingFace model download test
214+
├── tests/ # Comprehensive test suite
215+
│ ├── conftest.py # Shared test fixtures
216+
│ ├── unit/ # Unit tests for individual components
217+
│ │ ├── test_runpod_volume_workspace.py # Volume detection and initialization
218+
│ │ ├── test_volume_execution.py # Volume-aware execution
219+
│ │ └── test_*.py # Other unit tests
220+
│ └── integration/ # End-to-end integration tests
221+
│ ├── test_runpod_volume_integration.py # Volume workflow tests
222+
│ └── test_*.py # Other integration tests
117223
├── tetra-rp/ # Git submodule - Tetra SDK
118224
│ ├── src/tetra_rp/
119225
│ │ ├── client.py # @remote decorator
@@ -130,9 +236,13 @@ gpu_config = LiveServerless(
130236
- Docker images are automatically built and pushed to Docker Hub (`runpod/tetra-rp`) on release
131237

132238
### GitHub Actions Workflows
133-
- **Release** (`.github/workflows/release-please.yml`): Manages releases and versioning
134-
- **Docker Images [Prod]** (`.github/workflows/docker-prod.yml`): Builds and pushes Docker images on release
135-
- **Docker Images [Dev]** (`.github/workflows/docker-dev.yml`): Builds and pushes `:dev` tagged images on main branch pushes
239+
- **CI/CD** (`.github/workflows/ci.yml`): Single workflow handling tests, linting, releases, and Docker builds
240+
- Runs tests and linting on PRs and pushes to main
241+
- **Local execution testing**: Automatically tests all `test_*.json` files in root directory to validate handler functionality
242+
- Manages releases via `release-please` on main branch
243+
- Builds and pushes `:dev` tagged images on main branch pushes
244+
- Builds and pushes production images with semantic versioning on releases
245+
- **Deploy** (`.github/workflows/deploy.yml`): Manual deployment workflow for custom Docker tags and emergency deployments
136246

137247
### Required Secrets
138248
Configure these in GitHub repository settings:
@@ -141,5 +251,8 @@ Configure these in GitHub repository settings:
141251

142252
## Branch Information
143253
- Main branch: `main`
144-
- Current working branch: `dean/ae-518-cpu-live-serverless`
145-
- Submodule tracking: Updates pulled from remote automatically during setup
254+
- Submodule tracking: Updates pulled from remote automatically during setup
255+
256+
## Development Best Practices
257+
258+
- Always run `make quality-check` before committing changes

Dockerfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
1010
&& chmod +x /usr/local/bin/uv
1111

1212
# Copy app code and install dependencies
13-
COPY README.md remote_execution.py handler.py pyproject.toml uv.lock test_input.json ./
13+
COPY README.md src/* pyproject.toml uv.lock test_*.json ./
1414
RUN uv sync
1515

1616

Dockerfile-cpu

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
1111
&& chmod +x /usr/local/bin/uv
1212

1313
# Copy app files and install deps
14-
COPY README.md remote_execution.py handler.py pyproject.toml uv.lock ./
14+
COPY README.md src/* pyproject.toml uv.lock test_*.json ./
1515
RUN uv sync
1616

1717
# Stage 2: Runtime stage

Makefile

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ upgrade: # Upgrade all dependencies
3232
setup: dev # Initialize project, sync deps, update submodules
3333
git submodule init
3434
git submodule update --remote --merge
35-
cp tetra-rp/src/tetra_rp/protos/remote_execution.py .
35+
cp tetra-rp/src/tetra_rp/protos/remote_execution.py src/
3636

3737
build: setup # Build GPU Docker image (linux/amd64)
3838
docker buildx build \
@@ -65,6 +65,30 @@ test-coverage: # Run tests with coverage report
6565
test-fast: # Run tests with fast-fail mode
6666
uv run pytest tests/ -v -x --tb=short
6767

68+
test-handler: # Test handler locally with all test_*.json files
69+
@echo "Testing handler with all test_*.json files..."
70+
@failed_tests=""; \
71+
for test_file in test_*.json; do \
72+
if [ ! -f "$$test_file" ]; then \
73+
echo "No test_*.json files found"; \
74+
exit 1; \
75+
fi; \
76+
echo "Testing with $$test_file..."; \
77+
if env PYTHONPATH=src RUNPOD_TEST_INPUT="$$(cat "$$test_file")" uv run python src/handler.py >/dev/null 2>&1; then \
78+
echo "$$test_file: PASSED"; \
79+
else \
80+
exit_code=$$?; \
81+
echo "$$test_file: FAILED (exit code: $$exit_code)"; \
82+
failed_tests="$$failed_tests $$test_file"; \
83+
fi; \
84+
done; \
85+
if [ -z "$$failed_tests" ]; then \
86+
echo "All tests passed!"; \
87+
else \
88+
echo "Failed tests:$$failed_tests"; \
89+
exit 1; \
90+
fi
91+
6892
# Linting commands
6993
lint: # Check code with ruff
7094
uv run ruff check .
@@ -78,5 +102,9 @@ format: # Format code with ruff
78102
format-check: # Check code formatting
79103
uv run ruff format --check .
80104

105+
# Type checking
106+
typecheck: # Check types with mypy
107+
uv run mypy .
108+
81109
# Quality gates (used in CI)
82-
quality-check: format-check lint test-coverage # Run all quality checks (format, lint, test)
110+
quality-check: format-check lint typecheck test-coverage

0 commit comments

Comments
 (0)