Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
13 changes: 12 additions & 1 deletion .github/workflows/branch-e2e.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,19 @@ jobs:
component: gateway
platform: linux/arm64

build-supervisor:
needs: [pr_metadata]
if: needs.pr_metadata.outputs.should_run == 'true'
permissions:
contents: read
packages: write
uses: ./.github/workflows/docker-build.yml
with:
component: supervisor
platform: linux/arm64

e2e:
needs: [pr_metadata, build-gateway]
needs: [pr_metadata, build-gateway, build-supervisor]
if: needs.pr_metadata.outputs.should_run == 'true'
permissions:
contents: read
Expand Down
25 changes: 19 additions & 6 deletions .github/workflows/e2e-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,13 @@ jobs:
include:
- suite: python
cmd: "mise run --no-deps --skip-deps e2e:python"
- suite: rust
apt_packages: ""
- suite: rust-docker
cmd: "mise run --no-deps --skip-deps e2e:rust"
apt_packages: "openssh-client"
- suite: rust-podman
cmd: "mise run --no-deps --skip-deps e2e:podman"
Comment thread
drew marked this conversation as resolved.
apt_packages: "openssh-client podman"
container:
image: ghcr.io/nvidia/openshell/ci:latest
credentials:
Expand All @@ -50,16 +55,24 @@ jobs:
steps:
- uses: actions/checkout@v6

- name: Log in to GHCR
- name: Install OS test dependencies
if: matrix.apt_packages != ''
env:
APT_PACKAGES: ${{ matrix.apt_packages }}
run: apt-get update && apt-get install -y ${APT_PACKAGES} && rm -rf /var/lib/apt/lists/*
Comment thread
drew marked this conversation as resolved.

- name: Log in to GHCR with Docker
run: echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u "${{ github.actor }}" --password-stdin

- name: Log in to GHCR with Podman
if: matrix.suite == 'rust-podman'
run: echo "${{ secrets.GITHUB_TOKEN }}" | podman login ghcr.io -u "${{ github.actor }}" --password-stdin

- name: Install Python dependencies and generate protobuf stubs
if: matrix.suite == 'python'
run: uv sync --frozen && mise run --no-deps python:proto

- name: Install SSH client
if: matrix.suite != 'python'
run: apt-get update && apt-get install -y --no-install-recommends openssh-client && rm -rf /var/lib/apt/lists/*

- name: Run tests
env:
OPENSHELL_SUPERVISOR_IMAGE: ${{ format('ghcr.io/nvidia/openshell/supervisor:{0}', inputs.image-tag) }}
run: ${{ matrix.cmd }}
2 changes: 1 addition & 1 deletion .github/workflows/release-dev.yml
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ jobs:
cargo-version: ${{ needs.compute-versions.outputs.cargo_version }}

e2e:
needs: [build-gateway, build-cluster]
needs: [build-gateway, build-supervisor, build-cluster]
uses: ./.github/workflows/e2e-test.yml
with:
image-tag: ${{ github.sha }}
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/release-tag.yml
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ jobs:
cargo-version: ${{ needs.compute-versions.outputs.cargo_version }}

e2e:
needs: [build-gateway, build-cluster]
needs: [build-gateway, build-supervisor, build-cluster]
uses: ./.github/workflows/e2e-test.yml
with:
image-tag: ${{ github.sha }}
Expand Down
29 changes: 22 additions & 7 deletions TESTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -145,29 +145,43 @@ Rust-based e2e tests that exercise the `openshell` CLI binary as a subprocess.
They live in the `openshell-e2e` crate and use a shared harness for sandbox
lifecycle management, output parsing, and cleanup.

Tests:
Suites:

- `tests/custom_image.rs` — custom Docker image build and sandbox run
- `tests/sync.rs` — bidirectional file sync round-trip (including large files)
- `tests/port_forward.rs` — TCP port forwarding through a sandbox
- 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.

Run all CLI e2e tests:
Run the Docker-backed Rust CLI e2e suite:

```bash
```shell
mise run e2e:rust
```

Run the Podman-backed Rust CLI e2e suite:

```shell
mise run e2e:podman
```

Run a single test directly with cargo:

```bash
```shell
cargo test --manifest-path e2e/rust/Cargo.toml --features e2e --test sync
```

Run a single Docker-only test directly with cargo:

```shell
cargo test --manifest-path e2e/rust/Cargo.toml --features e2e-docker --test custom_image
```

The harness (`e2e/rust/src/harness/`) provides:

| Module | Purpose |
|---|---|
| `binary` | Builds and resolves the `openshell` binary from the workspace |
| `container` | Container-engine selection and support containers for proxy tests |
| `gateway` | Managed gateway restart controls for gateway-owned e2e runs |
| `sandbox` | `SandboxGuard` RAII type — creates sandboxes and deletes them on drop |
| `output` | ANSI stripping and field extraction from CLI output |
| `port` | `wait_for_port()` and `find_free_port()` for TCP testing |
Expand All @@ -178,3 +192,4 @@ The harness (`e2e/rust/src/harness/`) provides:
|---|---|
| `OPENSHELL_GATEWAY` | Override active gateway name for E2E tests |
| `OPENSHELL_GATEWAY_ENDPOINT` | Run E2E tests against an existing plaintext HTTP gateway endpoint |
| `OPENSHELL_E2E_DRIVER` | Driver name exported by the e2e gateway wrapper (`docker`, `podman`, or `vm`) |
55 changes: 49 additions & 6 deletions crates/openshell-driver-podman/src/container.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ fn is_selinux_enabled() -> bool {
pub const LABEL_SANDBOX_ID: &str = "openshell.sandbox-id";
/// Label key for the sandbox name.
pub const LABEL_SANDBOX_NAME: &str = "openshell.sandbox-name";
/// Label key for the sandbox namespace.
pub const LABEL_SANDBOX_NAMESPACE: &str = "openshell.sandbox-namespace";
Comment thread
drew marked this conversation as resolved.
/// Label applied to all managed containers.
pub const LABEL_MANAGED: &str = "openshell.managed";
/// Label filter string for list/event queries.
Expand Down Expand Up @@ -308,6 +310,7 @@ fn build_labels(sandbox: &DriverSandbox) -> BTreeMap<String, String> {
// Managed labels (highest priority -- always overwrite).
labels.insert(LABEL_SANDBOX_ID.into(), sandbox.id.clone());
labels.insert(LABEL_SANDBOX_NAME.into(), sandbox.name.clone());
labels.insert(LABEL_SANDBOX_NAMESPACE.into(), sandbox.namespace.clone());
labels.insert(LABEL_MANAGED.into(), "true".into());

labels
Expand Down Expand Up @@ -499,12 +502,13 @@ pub fn build_container_spec(sandbox: &DriverSandbox, config: &PodmanComputeConfi
secret_name(&sandbox.id),
)]),
stop_timeout: config.stop_timeout_secs,
// Inject host.containers.internal into /etc/hosts so sandbox
// containers can reach the gateway server on the host. The
// "host-gateway" magic value tells Podman to resolve to the
// host's actual IP (pasta uses 169.254.1.2 in rootless mode).
// This is the Podman equivalent of Docker's host.docker.internal.
hostadd: vec!["host.containers.internal:host-gateway".into()],
// Inject stable host aliases into /etc/hosts so sandbox containers can
// reach services on the host. `host.openshell.internal` is the driver-
// neutral alias used by policies and e2e tests.
hostadd: vec![
"host.containers.internal:host-gateway".into(),
"host.openshell.internal:host-gateway".into(),
],
netns: NetNS {
nsmode: "bridge".to_string(),
},
Expand Down Expand Up @@ -856,12 +860,17 @@ mod tests {
use openshell_core::proto::compute::v1::{DriverSandboxSpec, DriverSandboxTemplate};

let mut sandbox = test_sandbox("real-id", "real-name");
sandbox.namespace = "real-namespace".to_string();
let mut label_overrides = std::collections::HashMap::new();
label_overrides.insert("openshell.sandbox-id".to_string(), "spoofed-id".to_string());
label_overrides.insert(
"openshell.sandbox-name".to_string(),
"spoofed-name".to_string(),
);
label_overrides.insert(
"openshell.sandbox-namespace".to_string(),
"spoofed-namespace".to_string(),
);
sandbox.spec = Some(DriverSandboxSpec {
template: Some(DriverSandboxTemplate {
labels: label_overrides,
Expand All @@ -888,6 +897,40 @@ mod tests {
Some("real-name"),
"openshell.sandbox-name must not be overridden by template labels"
);
assert_eq!(
labels
.get("openshell.sandbox-namespace")
.and_then(|v| v.as_str()),
Some("real-namespace"),
"openshell.sandbox-namespace must not be overridden by template labels"
);
}

#[test]
fn container_spec_injects_host_aliases() {
let sandbox = test_sandbox("test-id", "test-name");
let config = test_config();
let spec = build_container_spec(&sandbox, &config);

let hostadd: Vec<&str> = spec["hostadd"]
.as_array()
.expect("hostadd should be an array")
.iter()
.filter_map(|v| v.as_str())
.collect();

assert!(
hostadd.contains(&"host.containers.internal:host-gateway"),
"missing Podman host alias"
);
assert!(
hostadd.contains(&"host.openshell.internal:host-gateway"),
"missing OpenShell stable host alias"
);
assert!(
!hostadd.contains(&"host.docker.internal:host-gateway"),
"Podman should not inject Docker's host alias"
);
}

#[test]
Expand Down
22 changes: 22 additions & 0 deletions e2e/rust/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,28 @@ publish = false

[features]
e2e = []
e2e-docker = ["e2e"]
e2e-docker-gpu = ["e2e-docker"]

[[test]]
name = "custom_image"
path = "tests/custom_image.rs"
required-features = ["e2e-docker"]

[[test]]
name = "docker_gpu"
path = "tests/docker_gpu.rs"
required-features = ["e2e-docker-gpu"]

[[test]]
name = "docker_preflight"
path = "tests/docker_preflight.rs"
required-features = ["e2e-docker"]

[[test]]
name = "gateway_resume"
path = "tests/gateway_resume.rs"
required-features = ["e2e-docker"]

[dependencies]
tokio = { version = "1.43", features = ["full"] }
Expand Down
3 changes: 2 additions & 1 deletion e2e/rust/e2e-docker.sh
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,12 @@ set -euo pipefail

ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
E2E_TEST="${OPENSHELL_E2E_DOCKER_TEST:-smoke}"
E2E_FEATURES="${OPENSHELL_E2E_DOCKER_FEATURES:-e2e,e2e-docker}"

cargo build -p openshell-cli --features openshell-core/dev-settings

exec "${ROOT}/e2e/with-docker-gateway.sh" \
cargo test --manifest-path "${ROOT}/e2e/rust/Cargo.toml" \
--features e2e \
--features "${E2E_FEATURES}" \
--test "${E2E_TEST}" \
-- --nocapture
Loading
Loading