Skip to content

test(conformance): port sandbox lifecycle and API e2e tests to testkit - #5431

Merged
elezar merged 20 commits into
mainfrom
test/5425-port-sandbox-lifecycle/matthewgrossman
Nov 13, 2026
Merged

elezar merged 20 commits into
mainfrom
test/5425-port-sandbox-lifecycle/matthewgrossman

Conversation

@matthewgrossman

@matthewgrossman matthewgrossman commented Nov 9, 2026 •

Copy link
Copy Markdown
Member

Summary

Moves the driver-agnostic sandbox lifecycle and API tests from e2e/rust to testkit so they run on every conformance row, moves the rest to the podman driver suite or crate tests, and deletes the legacy files it replaces. PR1 of the #5297 plan.

Related Issue

Closes #5425
Part of #5281

Changes

  • New conformance suites under tests/suites/conformance/src/: image, labels, templates, settings, process, filesystem_policy. New tests in lifecycle.rs and file_transfer.rs. The suite table in tests/suites/conformance/README.md is updated.
  • tests/suites/drivers/podman/tests/oci_identity.rs: supervisor container identity and capabilities.
  • Crate tests: crates/openshell-cli/tests/doctor_integration.rs (uses a fake docker on PATH) and crates/openshell-gateway/tests/proxy_config_preflight.rs.
  • testkit internals: Context, SandboxBuilder, Sandbox and CliSandboxBuilder share one Handles value (clients, CLI, cleanup), so CliSandboxBuilder owns its state like SandboxBuilder rather than borrowing cx. Both builders are #[must_use] types, and their setters take the same argument types.
  • Shared test helpers, used by the tests in this PR:
    • cx.cli_sandbox(suffix) → CliSandboxBuilder (.image(), .policy_yaml(yaml), .args(), .command(); .create() detaches and adopts, .run() returns the attached CliOutput). It tracks the sandbox before the CLI runs and passes --no-tty.
    • SandboxBuilder::image(..), so both builders set an image the same way, and create_ready() on both builders: create, then wait for Ready.
    • On Sandbox: poll_exec(what, timeout, command, ready) reruns an exec every 250 ms until ready accepts its output; get_json() is sandbox get --output json; wait_absent(timeout) waits for the gateway to remove the sandbox by itself, then checks get and list.
    • On both ExecOutput and CliOutput: success(), failure() and expect_stdout(expected), through one shared check so their messages match and report stdout and stderr. ExecOutput.description names the exec and its sandbox. CliOutput::report_text() (moved from file_transfer.rs) flattens a wrapped CLI error report for matching.
    • testkit::YamlFile (from test(conformance): port the credential gating e2e tests #5318) replaces this PR's private PolicyFile. CliSandboxBuilder::policy_yaml uses it.
    • testkit::with_deadline(what, timeout, future): a named deadline shorter than a step's own.
    • std::io::Error, serde_json::Error, serde_yml::Error and tonic::Status convert into testkit::Error, so I/O and gRPC results take .context() directly. A converted tonic::Status keeps its code for error.grpc_code(), which replaces Error::from_status. The .map_err(Error::from_error) calls in testkit, tests/support and the features suites are gone.
    • Engine::build_test_image(cx, name, containerfile) in container-engine-support: builds localhost/openshell-test-<name>:<run id> and removes it on cleanup. Engine::build_image is now private, so callers can't skip the cleanup.
  • Existing testkit API that changed, so the helpers are uniform: cx.expect_listed(&sandbox, phase) and cx.expect_absent(&sandbox) are now sandbox.expect_listed(phase) and sandbox.expect_absent(). The testkit README lists the current API.
  • These helpers replace duplicate code: the PolicyFile copies and hand-written sandbox create/sandbox get calls in host-gateway and oci-image (host-gateway now creates its sandbox with cx.sandbox(..).image(..).policy(..).create_ready() and runs the client with sandbox.exec), the image build/cleanup in oci-image and podman oci_identity, and the create in podman userns. host-gateway drops its tempfile dependency and oci-image drops its serde_json dependency. The proposed CLI-delete cleanup helper was skipped because no test in this PR uses it.
  • ExecOutput::success() also replaces the hand-written exit-code checks in the existing smoke, lifecycle, persistent_workspace, file_transfer and policy_advisor conformance tests. Polls and checks that expect a failure still read exit_code.
  • The other helpers are used across the conformance suite, existing tests included. credential_gating.rs (from test(conformance): port the credential gating e2e tests #5318) uses create_ready() and poll_exec. templates.rs, settings.rs and labels.rs also get small local helpers (create_template, read_global_setting, listed_names).
  • Deleted 10 legacy files and their references: the [[test]] entries, PODMAN_CI_TESTS and TESTING.md. Also removed the now-dead SandboxGuard::create_with_gateway_default.
  • Stay in e2e/rust until their batches: edge_tunnel_e2e.rs (with --enable-websocket-tunnel true in e2e/with-{docker,podman}-gateway.sh) and ephemeral_cleanup.rs (on the Podman and VM lanes).
  • Stay in e2e/rust until their blockers are fixed: sandbox_lifecycle::on_failure_policy_replaces_runtime_and_preserves_workspace (bug(vm): on-failure restart never completes because the preserved overlay fails e2fsck #5507) and settings_management.rs (test(testkit): separate destructive tests from non-destructive ones #5527).
  • Shared files:

Outcome per legacy test

Legacy test Outcome
sandbox_lifecycle::sandbox_stop_start_preserves_workspace Merged: already covered by persistent_workspace_survives_stop_start. "Stop while connect is open" moves to PR4.
sandbox_lifecycle::sandbox_can_be_deleted_while_stopped Merged: covered by lifecycle::stopped_sandbox_can_be_deleted
sandbox_lifecycle::sandbox_exec_outlives_startup_recovery_deadline lifecycle::exec_outlives_startup_recovery_deadline
sandbox_lifecycle::detached_canonical_main_{exit_zero_reaches_completed,nonzero_exit_reaches_error} lifecycle::main_exit_after_ready_sets_final_phase
sandbox_lifecycle::canonical_main_and_exec_receive_declared_environment + canonical_tty_main_uses_sandbox_environment lifecycle::canonical_main_environment
sandbox_lifecycle::detached_main_exit_during_provisioning_is_classified_as_workload_result lifecycle::main_exit_during_provisioning_is_workload_result
sandbox_lifecycle::on_failure_policy_replaces_runtime_and_preserves_workspace Stays in e2e/rust until #5507 (on the VM driver the replacement never starts). The legacy test runs on the Docker and Kubernetes lanes.
ephemeral_cleanup::detached_ephemeral_{success,failure}_removes_sandbox_and_driver_resources Gateway half added as lifecycle::detached_no_keep_sandbox_is_removed_after_main_exits; the legacy test stays for the driver-resource half
landlock::hard_requirement_accepts_enriched_device_path filesystem_policy::hard_requirement_accepts_added_device_paths
landlock::{best_effort,hard_requirement}_rejects_replaced_policy_{path,ancestor} (4) filesystem_policy::replaced_policy_path_rejects_next_launch
core_dump_hardening::sandbox_processes_disable_core_dumps process::core_dumps_are_disabled
default_image::sandbox_from_default_image Merged into lifecycle::default_command_sandbox_is_listed_ready
default_image::sandbox_from_explicit_nvidia_ubuntu_image image::explicit_image_is_used
sandbox_labels::sandbox_labels_are_stored_and_filterable labels::labels_are_stored_and_filter_list
sandbox_templates::sandbox_create_from_template_uses_reusable_workload templates::sandbox_from_template_uses_its_workload
sandbox_templates:: get-after-delete, duplicate name, missing template (3) templates::template_crud_errors
settings_management::settings_global_override_round_trip Split: the sandbox scope is migrated as settings::sandbox_setting_round_trip; the global override stays in e2e/rust until #5527
upload_backpressure::large_upload_preserves_content_and_metadata_outside_workdir Split: file_transfer::large_upload_backpressure + the outside-workdir metadata case in file_transfer
podman_oci_identity::podman_supervisor_runs_as_oci_identity_without_capabilities drivers/podman oci_identity::supervisor_runs_as_oci_identity_without_capabilities
docker_preflight::doctor_check_fails_without_docker + doctor_check_output_shows_docker_label doctor_check_fails_without_docker_on_path (+ doctor_check_fails_fast_when_docker_info_fails)
docker_preflight::doctor_check_passes_with_docker doctor_check_passes_when_docker_info_succeeds
docker_preflight::doctor_check_error_includes_guidance Dropped
docker_corporate_proxy::…rejects_incoherent_configuration + vm_corporate_proxy::…rejects_incoherent_configuration Dropped → crate test proxy_config_preflight.rs (docker, podman, vm)
docker_corporate_proxy::proxy_settings_are_inserted_before_nested_driver_tables Stays in docker_corporate_proxy.rs
edge_tunnel_e2e (2 tests) Stays in e2e/rust until its batch
internet_network_perf::benchmark_complete_internet_path Dropped (#5472)
live_internet_traffic_perf::benchmark_live_internet_agent_traffic Dropped (#5472)

Decisions and defaults applied

  • The drops follow decision D in test(e2e): decide which e2e/rust tests to port and in what order #5297, and the work ships as one PR.
  • large_upload_backpressure runs on every conformance row.
  • Conformance tests don't change gateway-wide settings. sandbox_setting_round_trip reads the global value of ocsf_json_enabled and fails with a hint if one is set, because it would hide the sandbox's value. The legacy test cleared it instead.
  • The podman OCI identity test runs on all 8 userns rows, rootful included, and passes on each.
  • The proxy preflight test checks both config preflight (category=malformed, file unchanged) and real openshell-gateway --config startup, because only startup names the offending keys.
  • Landlock tests now run on every row, through exec rather than as the main command with CLI --policy YAML. Their policy sets no process identity, so they run as the image's USER. The default test image has no sandbox user. Rows without Landlock fail hard_requirement, and they should.
  • Test deadlines fit inside the runner's 2-minute kill.
  • The labels test waits for its sandboxes to be Ready. Deleting a Docker sandbox that is still starting can fail with a 409 "volume is in use" error (bug(docker): deleting a sandbox while it is starting fails with "volume is in use" #5459); a conformance test for that lands with the fix.
  • proxy_config_preflight.rs compiles only when the Docker, Podman or VM driver is built in, because the gateway feature matrix also builds without them. The --no-keep test releases both sandboxes before waiting, with a 60 s limit.

What was dropped or moved

Testing

  • CI: every E2E row is green, including the Docker, Podman (all 8 userns rows), Kubernetes and VM conformance rows. The k3s-ha conformance row needed re-runs for an unrelated flake (bug(e2e): file_transfer::upload_download_round_trip stalls on the k3s-ha conformance row #5520).
  • mise run e2e:testkit on a local Docker gateway: the full conformance suite passes (the #[ignore]d bug(sandbox): on Kubernetes, deleting a sandbox waits out the pods' termination grace period #5025 test is skipped, as on main), plus the oci-image and host-gateway feature suites
  • cargo test for the new crate tests on macOS: doctor_integration, proxy_config_preflight; proxy_config_preflight also builds under the CI feature matrix (no drivers, Kubernetes only, Docker)
  • clippy -D warnings and fmt: openshell-server, -cli, -gateway, the conformance, podman and features suites, tests/support, testkit, e2e/rust
  • mise run rust:lockfiles:check
  • cargo check on e2e/rust with e2e, e2e-podman, e2e-docker and e2e-vm

Checklist

  • Follows Conventional Commits
  • Commits are signed off (DCO)
  • Architecture docs updated (if applicable)

@matthewgrossman matthewgrossman added the test:e2e Requires end-to-end coverage label Nov 9, 2026
@matthewgrossman

Copy link
Copy Markdown
Member Author

/ok to test c2eceee

@github-actions

github-actions Bot commented Nov 9, 2026

Copy link
Copy Markdown

Label test:e2e applied for c2eceee. Open the existing run and click Re-run all jobs to execute with the label set. The run will execute the standard E2E suite after building the required gateway, sandbox, and supervisor images once. The matching required CI gate status on this PR will flip green automatically once the run finishes.

@matthewgrossman

Copy link
Copy Markdown
Member Author

/ok to test 45180d0

@matthewgrossman

Copy link
Copy Markdown
Member Author

/ok to test e3b0f27

@matthewgrossman

Copy link
Copy Markdown
Member Author

/ok to test 73e5bff

@matthewgrossman

Copy link
Copy Markdown
Member Author

/ok to test caff38c

@matthewgrossman

Copy link
Copy Markdown
Member Author

/ok to test 2cefa9a

@matthewgrossman
matthewgrossman force-pushed the test/5425-port-sandbox-lifecycle/matthewgrossman branch from 2cefa9a to 1ce77a5 Compare November 10, 2026 05:45
@matthewgrossman
matthewgrossman changed the base branch from main to test/5296-credential-gating-pilot/matthewgrossman November 10, 2026 05:45
@matthewgrossman

Copy link
Copy Markdown
Member Author

/ok to test 1ce77a5

@github-actions

Copy link
Copy Markdown

Base automatically changed from test/5296-credential-gating-pilot/matthewgrossman to main November 10, 2026 09:44
@elezar
elezar force-pushed the test/5425-port-sandbox-lifecycle/matthewgrossman branch from 1ce77a5 to f8b1ced Compare November 10, 2026 10:44
@matthewgrossman

Copy link
Copy Markdown
Member Author

/ok to test a96df3a

@matthewgrossman
matthewgrossman force-pushed the test/5425-port-sandbox-lifecycle/matthewgrossman branch from f8b1ced to a96df3a Compare November 10, 2026 17:40
@matthewgrossman

Copy link
Copy Markdown
Member Author

/ok to test 034814b

@matthewgrossman

Copy link
Copy Markdown
Member Author

/ok to test 1ed49ce

@matthewgrossman

Copy link
Copy Markdown
Member Author

/ok to test 163fa4d

@matthewgrossman

Copy link
Copy Markdown
Member Author

/ok to test 1cd665d

@matthewgrossman

Copy link
Copy Markdown
Member Author

/ok to test 139dc5d

@elezar

elezar commented Nov 12, 2026

Copy link
Copy Markdown
Member

@matthewgrossman I pushed one commit on top of your branch (b013446be) that rolls two tests back out of this port:

The README rows for lifecycle and settings are updated to match.

Please update the PR description:

Your local branch now has a new commit on top, so git pull is enough; no reset is needed this time.

…e helpers

Add SandboxBuilder::create_ready, ExecOutput::expect_stdout, Sandbox::poll_exec, Context::delete_and_expect_absent and testkit::with_deadline, and convert std::io::Error into testkit::Error so I/O results take .context directly. Use them across the conformance suite, which also gets shared template, global-setting and label-list helpers and a split on-failure restart poll. Make Engine::build_image private so callers go through build_test_image, which removes the image.

Signed-off-by: Matthew Grossman <mgrossman@nvidia.com>
…proxy

Replace the hand-written WebSocket bridge with openshell_sdk::edge_tunnel::start_tunnel_proxy, so the test covers the client half of the tunnel and its edge-token headers as well as the router.

Signed-off-by: Matthew Grossman <mgrossman@nvidia.com>
Signed-off-by: Matthew Grossman <mgrossman@nvidia.com>
Move expect_listed, expect_absent, wait_absent and the CLI's sandbox get JSON (now get_json) from Context onto Sandbox, and drop delete_and_expect_absent. Give ExecOutput failure() and CliOutput expect_stdout(), so both outputs offer the same checks. Put with_deadline's description first, like poll_until. Make CliSandboxBuilder::run consume the builder like create, and rename its policy setter to policy_yaml. Update the testkit README's list of what a test can do.

Signed-off-by: Matthew Grossman <mgrossman@nvidia.com>
Replace CliSandboxBuilder's private PolicyFile with testkit::YamlFile, and drop YamlFile's map_err calls now that I/O errors convert into testkit::Error. CliOutput::success now includes stdout in its failure message, like ExecOutput::success, since many commands report errors there.

Signed-off-by: Matthew Grossman <mgrossman@nvidia.com>
…ting changes

Add SandboxBuilder::image and CliSandboxBuilder::create_ready, so both builders set an image and wait for Ready the same way. Convert tonic::Status into testkit::Error, keeping its code, so gRPC results take .context() directly, and drop Error::from_status. Create the host-gateway sandbox with cx.sandbox(..).image(..).policy(..) instead of a hand-written CLI create. In credential gating, use create_ready and poll_exec. Drop the remaining map_err calls on I/O results in host-server-support.

Signed-off-by: Matthew Grossman <mgrossman@nvidia.com>
…ble driver

The gateway feature matrix builds without Docker, Podman or the VM driver, where the test file has no drivers to check.

Signed-off-by: Matthew Grossman <mgrossman@nvidia.com>
…ure restart

Deleting a Docker sandbox that is still starting fails with a 409 because its supervisor volume is in use, so the labels test now waits for Ready before cleanup. The VM driver needs more than 45s to report an on-failure restart; the original e2e test allowed 60s.

Signed-off-by: Matthew Grossman <mgrossman@nvidia.com>
…lders

Context, SandboxBuilder, Sandbox and CliSandboxBuilder now share one Handles value (clients, CLI, cleanup), so CliSandboxBuilder owns its state like SandboxBuilder instead of borrowing the context. Both builders are #[must_use] at the type level, and CliSandboxBuilder::image and policy_yaml take impl Into<String> like SandboxBuilder::image.

ExecOutput and CliOutput run success, failure and expect_stdout through one shared check, so their messages match and failure() also reports stderr. ExecOutput.command is renamed description, since it names the exec and its sandbox. CliOutput::message is renamed report_text.

serde_json and serde_yml errors convert into testkit::Error, which removes the last map_err(Error::from_error) calls. expect_listed uses let-else instead of ensure plus expect. kubernetes-updates uses create_ready.

Signed-off-by: Matthew Grossman <mgrossman@nvidia.com>
@elezar
elezar force-pushed the test/5425-port-sandbox-lifecycle/matthewgrossman branch from b5f0737 to 10e797e Compare November 13, 2026 09:43
elezar
elezar previously approved these changes Nov 13, 2026

@elezar elezar left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-approving after the rebase onto current main (10e797e2b). The only conflict was in e2e/rust/Cargo.toml: I kept main's new supervisor_middleware_http_hooks test entry and dropped the sandbox_templates entry that this PR deletes. The rest of the diff is unchanged. cargo check passes on e2e/rust and the conformance suite. CI still needs to run on the new head.

matthewgrossman and others added 8 commits November 13, 2026 11:01
Read errors in gateway.rs and the image test use .context(..) now that the error types convert. Error::from_error and from_display are crate-private. Document why the global-setting helpers take &Cli, why the template test names and adopts its sandbox by hand, SandboxBuilder::restart_policy, and that the --no-keep test checks only gateway-visible removal.

Signed-off-by: Matthew Grossman <mgrossman@nvidia.com>
Restore edge_tunnel_e2e with the --enable-websocket-tunnel flag in the Docker and Podman gateway wrappers, and ephemeral_cleanup with its Podman and VM lane entries. Both check what conformance cannot: the tunnel flag's wiring through a real gateway, and that drivers remove their resources. They move in later batches.

Remove the in-process WebSocket tunnel server test and its openshell-sdk dev-dependency, which checked the listener but not the flag wiring. The --no-keep conformance test now says the driver-resource check stays in ephemeral_cleanup.

Signed-off-by: Matthew Grossman <mgrossman@nvidia.com>
The two live-Internet benchmarks were manual, needed the live Internet, and nothing referenced them. Remove the Python port and its perf:live-internet task instead of keeping it. #5472 tracks whether to bring one back.

Signed-off-by: Matthew Grossman <mgrossman@nvidia.com>
proxy_settings_are_inserted_before_nested_driver_tables covers insert_docker_driver_settings, which the remaining corporate proxy tests still use. It was removed with the incoherent-configuration test by mistake.

Signed-off-by: Matthew Grossman <mgrossman@nvidia.com>
On the VM driver the replacement sandbox never starts: the preserved overlay disk fails e2fsck, and the sandbox stays in Starting. The test fails on every VM lane. Ignore it with a link to #5507, which also tracks removing the ignore.

Signed-off-by: Matthew Grossman <mgrossman@nvidia.com>
…2e/rust

Restore `on_failure_policy_replaces_runtime_and_preserves_workspace` in
`sandbox_lifecycle.rs` and `settings_management.rs` as they were, and drop
their conformance counterparts.

The on-failure restart test ran on the Docker and Kubernetes lanes but never
on VM or Podman. Ported to conformance it fails on every VM row (#5507), and
an `#[ignore]` there removes the coverage it gave Docker and Kubernetes.

The global settings test changes a setting for the whole gateway, so it can't
share a conformance run with other tests until the runner has a test group
for it (#5295). `sandbox_setting_round_trip` stays in conformance.

Signed-off-by: Evan Lezar <elezar@nvidia.com>
… trip

sandbox_setting_round_trip deleted the gateway-wide ocsf_json_enabled value and restored it on cleanup, which changes every sandbox on a shared gateway while it runs. Fail with a hint when a global value is set instead, as policy_advisor does.

Signed-off-by: Matthew Grossman <mgrossman@nvidia.com>
Signed-off-by: Matthew Grossman <mgrossman@nvidia.com>
@elezar
elezar force-pushed the test/5425-port-sandbox-lifecycle/matthewgrossman branch from 10e797e to 36d882d Compare November 13, 2026 10:01
@elezar
elezar enabled auto-merge November 13, 2026 10:04
@elezar
elezar added this pull request to the merge queue Nov 13, 2026
Merged via the queue into main with commit b4a6541 Nov 13, 2026
156 of 158 checks passed
@elezar
elezar deleted the test/5425-port-sandbox-lifecycle/matthewgrossman branch November 13, 2026 10:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

test:e2e Requires end-to-end coverage

Projects

None yet

Development

Successfully merging this pull request may close these issues.

test(e2e): port sandbox lifecycle and API tests from e2e/rust to conformance

2 participants