# E2E Testing Architecture
This document explains the architectural decisions behind the E2E testing system, including the split testing approach and Docker-based deployment workflow validation.
## ποΈ Overall Architecture
The split E2E testing architecture ensures reliable CI while maintaining comprehensive coverage:
```text
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β E2E Test Suites β
βββββββ¬βββββββββββββββββ¬βββββββββββββββββββ¬ββββββββββββββββββββββββββ
β β β
β β β
βββββββΌβββββββ βββββββΌβββββββββββ βββββΌβββββββββββββββββββ
β Provision β βConfiguration β β Full Local β
β Tests β β Tests β β Tests β
β β β β β β
β LXD VMs β β Docker β β LXD VMs + Docker β
β (CI Safe) β β Containers β β (Local Only) β
β β β (CI Safe) β β β
βββββββ¬βββββββ βββββββββ¬βββββββββ βββββ¬βββββββββββββββββββ
β β β
βββββββΌβββββββββ βββββββΌβββββββββ βββββΌβββββββββββββββββββ
β OpenTofu/ β β Testcontain- β β OpenTofu + Ansible β
β LXD β β ers β β (Full Stack) β
βInfrastructureβ β Docker β β β
β Layer β β Management β β β
ββββββββββββββββ ββββββββββββββββ ββββββββββββββββββββββββ
β β β
ββββββββΌβββββββ ββββββββΌβββββββββββ βββββββββββΌββββββββββ
β VM Creation β βAnsible Playbooksβ β Complete Stack β
β Cloud-init β β Configuration β β Validation β
β Validation β β Validation β β β
βββββββββββββββ βββββββββββββββββββ βββββββββββββββββββββ
```
## π― Test Suite Responsibilities
- **Infrastructure Lifecycle Tests**: Infrastructure creation and basic VM setup validation
- **Deployment Workflow Tests**: Software installation and application deployment
- **Complete Workflow Tests**: End-to-end integration validation for comprehensive testing
This architecture provides:
1. **Reliability**: Each test suite works independently in CI environments
2. **Speed**: Focused testing reduces execution time
3. **Coverage**: Combined suites provide complete deployment validation
4. **Debugging**: Clear separation makes issue identification easier
## π³ Docker Architecture for Deployment Workflow Testing
The E2E testing system uses a Docker-based architecture for testing the deployment workflow commands (configure, release, run, test) efficiently and reliably in CI environments.
### Architecture Decision: Single Image with Sequential Command Execution
We use a **single Docker image** (`provisioned-instance`) representing the pre-provisioned state, and execute all deployment commands **sequentially** within that container during E2E tests.
**Why Sequential Instead of Multi-Image?**
Initially, we considered creating separate Docker images for each deployment phase (configured, released, running). However, this approach was **rejected** due to:
- **High Maintenance Overhead**: Every code change would require updating multiple Docker images
- **Slower Execution**: Building 4 images takes longer than running 4 commands sequentially
- **Synchronization Complexity**: Keeping multiple images in sync with code changes is error-prone
- **No Real Benefit**: Parallel test execution overhead (Docker build + startup) exceeds sequential execution time
**Sequential Execution Benefits**:
- β
**Single Source of Truth**: One Dockerfile to maintain
- β
**Faster Overall**: Sequential commands in one container (~48s) vs multiple image builds
- β
**Realistic Testing**: Matches real deployment workflow exactly
- β
**Easy Debugging**: Single container lifecycle with `--keep` flag
- β
**Automatic Synchronization**: Code changes tested via Ansible playbooks without image rebuilds
**Trade-offs Accepted**:
- β Cannot test individual commands in isolation (use unit/integration tests for that)
- β Cannot run E2E tests for different commands in parallel
- β Must run full sequence to test later commands
See [ADR: Single Docker Image for Sequential E2E Command Testing](../decisions/single-docker-image-sequential-testing.md) for the complete architectural decision.
### Current Implementation
#### Provisioned Instance (`docker/provisioned-instance/`)
**Purpose**: Represents the state after VM provisioning but before configuration.
**Contents**:
- Ubuntu 24.04 LTS base (matches production VMs)
- SSH server (via supervisor for container-native process management)
- `torrust` user with sudo access
- No application dependencies installed
- Ready for Ansible configuration
**E2E Test Workflow**:
```rust
// E2E deployment workflow tests (simplified)
async fn run_deployment_workflow_tests() -> Result<()> {
// 1. Start single container (provisioned state)
let container = start_provisioned_container().await?;
// 2. Run deployment commands sequentially
run_create_command()?; // Create environment
run_register_command()?; // Register container IP
run_configure_command()?; // Install dependencies (modifies container)
run_release_command()?; // Deploy applications (modifies container)
run_run_command()?; // Start services (modifies container)
run_test_command()?; // Validate deployment
// 3. Cleanup
container.stop().await?;
Ok(())
}
```
**Key Characteristics**:
- **Stateful Testing**: Each command modifies the container state for the next command
- **Complete Workflow**: Tests the full deployment pipeline end-to-end
- **Fast Execution**: ~48 seconds total (container start + all commands + validation)
- **CI Reliable**: Avoids GitHub Actions connectivity issues with LXD VMs
### Benefits of Single-Image Sequential Architecture
1. **Low Maintenance**: Single Dockerfile, changes propagate automatically via playbooks
2. **Realistic Testing**: Sequential execution matches real deployment workflow exactly
3. **Fast Feedback**: Faster than building multiple images, comparable to parallel execution
4. **Simple Debugging**: Use `--keep` flag to inspect final container state
5. **CI Reliability**: Single container uses fewer resources, avoids VM networking issues
6. **Code Synchronization**: Ansible playbooks ensure image reflects current code
### Testing Strategy
**What This Tests**:
- β
Complete deployment workflow (create β register β configure β release β run β test)
- β
Command integration and state transitions
- β
Ansible playbook execution in container environment
- β
Service deployment and validation
**What This Doesn't Test**:
- β Individual command isolation (use unit tests)
- β Infrastructure provisioning (use `e2e-infrastructure-lifecycle-tests`)
- β VM-specific features (use `e2e-complete-workflow-tests` locally)
## π Container vs VM Trade-offs
| **Network Reliability (CI)** | β
Excellent | β Poor (GitHub Actions issues) |
| **Startup Time** | β
~2-3 seconds | β οΈ ~17-30 seconds |
| **Production Similarity** | β οΈ Container (different from VMs) | β
Full VM (matches production) |
| **Resource Usage** | β
Lightweight | β οΈ Higher overhead |
| **Best For** | Configuration/deployment workflow | Infrastructure provisioning |
**Result**: Use Docker containers for deployment workflow tests, LXD VMs for infrastructure tests.
## π Why the Split Approach?
### CI Network Issues
**Problem**: GitHub Actions runners experience intermittent network connectivity problems within LXD VMs that cause:
- Docker GPG key downloads to fail (`Network is unreachable` errors)
- Package repository access timeouts
- Generally flaky network behavior
**Root Cause**: This is a known issue with GitHub-hosted runners:
- [GitHub Issue #13003](https://github.com/actions/runner-images/issues/13003) - Network connectivity issues with LXD VMs
- [GitHub Issue #1187](https://github.com/actions/runner-images/issues/1187) - Original networking issue
- [GitHub Issue #2890](https://github.com/actions/runner-images/issues/2890) - Specific apt repository timeout issues
**Solution**: We split E2E tests into two suites:
- **Infrastructure Lifecycle Tests**: Use LXD VMs for infrastructure testing only (no network-heavy operations inside VM)
- **Deployment Workflow Tests**: Use Docker containers which have reliable network connectivity on GitHub Actions
- **Complete Workflow Tests**: Available for comprehensive local testing where network connectivity works
**Implementation**: Deployment workflow tests use Docker containers with:
- Direct internet access for package downloads
- Reliable networking for Ansible connectivity
- No nested virtualization issues
## π― Test Design Principles
- **Infrastructure tests**: Focus on infrastructure readiness, minimal network dependencies
- **Deployment tests**: Focus on software functionality, reliable network access via containers
- **Complete tests**: Comprehensive validation for development workflows
- **Independence**: Each suite should be runnable independently without conflicts
The split E2E testing approach ensures reliable CI while maintaining comprehensive coverage of the entire deployment pipeline.