diff --git a/.github/workflows/e2e-test.yml b/.github/workflows/e2e-test.yml index ea870d98e..cb7bc67d1 100644 --- a/.github/workflows/e2e-test.yml +++ b/.github/workflows/e2e-test.yml @@ -205,3 +205,67 @@ jobs: - name: Print AppArmor denials if: always() run: sudo dmesg | grep -E 'apparmor=.*DENIED|profile="unprivileged_userns"' | tail -100 || true + + e2e-vm: + name: E2E (rust-vm) + # libkrun needs KVM, so this job must run directly on a GitHub-hosted + # Linux VM. GitHub-hosted macOS runners do not support nested + # virtualization, and a job container would hide the host KVM device. + runs-on: ubuntu-24.04 + timeout-minutes: 90 + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + MISE_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + steps: + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + ref: ${{ inputs['checkout-ref'] || github.sha }} + persist-credentials: false + + - name: Enable KVM access + run: | + set -euo pipefail + if [[ ! -c /dev/kvm ]]; then + echo "::error::The GitHub-hosted runner did not expose /dev/kvm" + lscpu + grep -m1 -E '^(flags|Features)' /proc/cpuinfo || true + ls -la /dev + exit 1 + fi + sudo chmod 0666 /dev/kvm + ls -l /dev/kvm + test -r /dev/kvm + test -w /dev/kvm + + - name: Install system dependencies + run: | + sudo apt-get update + sudo apt-get install -y --no-install-recommends \ + build-essential \ + clang \ + cmake \ + libclang-dev \ + libssl-dev \ + libz3-dev \ + openssh-client \ + pkg-config \ + socat \ + zstd + + - name: Install mise + run: | + curl https://mise.run | MISE_VERSION=v2026.4.25 sh + echo "$HOME/.local/bin" >> "$GITHUB_PATH" + echo "$HOME/.local/share/mise/shims" >> "$GITHUB_PATH" + + - name: Install tools + run: mise install --locked + + - name: Cache Rust target and registry + uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2 + with: + shared-key: e2e-vm-linux-amd64 + cache-on-failure: "true" + + - name: Run VM E2E + run: mise run --no-deps --skip-deps e2e:vm diff --git a/CI.md b/CI.md index e14d5bdb9..19fad7bd3 100644 --- a/CI.md +++ b/CI.md @@ -14,7 +14,8 @@ Merge queue validation is a second integration gate for `main`. After a PR has p Three opt-in labels enable the long-running E2E suites: -- `test:e2e` runs the standard E2E suite in `Branch E2E Checks` +- `test:e2e` runs the standard Docker, rootless Podman, Kubernetes, and VM E2E + suites in `Branch E2E Checks` - `test:e2e-gpu` runs GPU E2E in `Branch E2E Checks` - `test:e2e-kubernetes` runs Kubernetes E2E with the HA Helm overlay (`replicaCount: 2` and bundled PostgreSQL) in `Branch E2E Checks` diff --git a/TESTING.md b/TESTING.md index d32dc385c..a032baa5e 100644 --- a/TESTING.md +++ b/TESTING.md @@ -150,6 +150,7 @@ Suites: - Common suite (`--features e2e`) - driver-neutral CLI behavior, sandbox lifecycle, sync, port forwarding, policy, and provider tests. - Docker suite (`--features e2e-docker`) - common suite plus Docker-only coverage such as Dockerfile image builds, Docker preflight checks, and managed Docker gateway resume. - Docker GPU suite (`--features e2e-docker-gpu`) - Docker suite plus GPU sandbox smoke coverage. +- VM suite (`--features e2e-vm`) - runs e2e tests on a VM. GPU device-selection tests compare OpenShell sandboxes against a plain Docker or Podman container that requests `--device nvidia.com/gpu=all`. The probe image @@ -173,6 +174,12 @@ Run the Podman-backed Rust CLI e2e suite: mise run e2e:podman ``` +Run the VM-backed Rust CLI e2e suite: + +```shell +mise run e2e:vm +``` + Run a single test directly with cargo: ```shell diff --git a/architecture/build.md b/architecture/build.md index e3ac5012e..63d4e5684 100644 --- a/architecture/build.md +++ b/architecture/build.md @@ -148,7 +148,8 @@ The high-level CI model: 1. PR-context gate jobs publish required statuses for the PR head commit. 2. Standard branch checks run from trusted mirror branches. -3. Label-gated E2E, GPU, and Kubernetes checks run from trusted mirror branches. +3. Label-gated Docker, Podman, VM, GPU, and Kubernetes E2E checks run from + trusted mirror branches. 4. Merge-group checks run against GitHub's temporary queue branch for the final integration state. 5. Gate jobs verify that the mirror branch matches the PR head, or that the merge-group workflow ran for the queued SHA, and that the expected non-gate workflow actually ran. 6. Release workflows rebuild and publish binaries, wheels, images, and docs. diff --git a/e2e/rust/e2e-vm.sh b/e2e/rust/e2e-vm.sh index ef798a8ea..8005d09bd 100755 --- a/e2e/rust/e2e-vm.sh +++ b/e2e/rust/e2e-vm.sh @@ -315,4 +315,5 @@ if [ -n "${E2E_TEST_OVERRIDE}" ]; then else run_e2e_test smoke run_e2e_test host_gateway_alias + run_e2e_test vm_gateway_resume fi diff --git a/e2e/rust/tests/vm_gateway_resume.rs b/e2e/rust/tests/vm_gateway_resume.rs index b2ec6c8b5..4c502bb9c 100644 --- a/e2e/rust/tests/vm_gateway_resume.rs +++ b/e2e/rust/tests/vm_gateway_resume.rs @@ -36,8 +36,11 @@ async fn vm_gateway_restart_resumes_running_sandbox() { .await .expect("gateway should start healthy"); + // The gateway restart terminates the VM process before re-adopting its + // overlay. Flush the marker before reporting readiness so the assertion + // verifies durable overlay state rather than guest page-cache timing. let script = format!( - "echo before-restart > {RESUME_FILE}; echo {READY_MARKER}; while true; do sleep 1; done" + "echo before-restart > {RESUME_FILE}; sync; echo {READY_MARKER}; while true; do sleep 1; done" ); let mut sandbox = SandboxGuard::create_keep(&["sh", "-lc", &script], READY_MARKER) .await