Skip to content

Merge pull request #1147 from Dstack-TEE/feat/gateway-e2e-compose #268

Merge pull request #1147 from Dstack-TEE/feat/gateway-e2e-compose

Merge pull request #1147 from Dstack-TEE/feat/gateway-e2e-compose #268

# SPDX-FileCopyrightText: © 2026 Phala Network <dstack@phala.network>
#
# SPDX-License-Identifier: Apache-2.0
name: Gateway proxy tests
# The gateway's proxy data path has two opt-in optimisations (`tcp_splice`,
# `ktls`) whose behaviour depends on kernel capabilities and on a per-connection
# gate. Unit tests cover the relay functions; this runs a real gateway process
# and asserts on what actually reaches the wire.
#
# The suite runs in containers. One of them has a seccomp profile that makes
# `setsockopt(IPPROTO_TCP, TCP_ULP)` fail, which is how the no-TLS-ULP fallback
# is exercised now -- the suite used to `rmmod tls`, which needed passwordless
# sudo, took the module from the whole host, and skipped itself whenever
# anything else was using TLS.
on:
push:
branches: [ next, 'release/**' ]
paths:
- 'dstack/gateway/**'
- 'dstack/vendor/ktls/**'
# The suite runs the gateway with attestation on, so it depends on the
# shared fixture: the simulator, the mock collateral service, and the
# fixture payloads baked into the simulator image.
- 'dstack/guest-agent-simulator/**'
- 'dstack/crates/mock-attestation/**'
- 'sdk/simulator/**'
- '.github/workflows/gateway-proxy-tests.yml'
pull_request:
branches: [ next, 'release/**' ]
paths:
- 'dstack/gateway/**'
- 'dstack/vendor/ktls/**'
# The suite runs the gateway with attestation on, so it depends on the
# shared fixture: the simulator, the mock collateral service, and the
# fixture payloads baked into the simulator image.
- 'dstack/guest-agent-simulator/**'
- 'dstack/crates/mock-attestation/**'
- 'sdk/simulator/**'
- '.github/workflows/gateway-proxy-tests.yml'
# Per workflow, not shared with the other two gateway suites.
#
# A group shared across all three does not serialise them, it drops one: a
# concurrency group holds a single *pending* run, so when the third workflow
# queued behind the first two, the one already waiting was cancelled --
# silently, and reported as a cancelled run rather than a failure. The suites
# no longer need serialising anyway; each brings up its own attestation fixture
# under its own `FIXTURE_NS`, so there is nothing left to collide over.
#
# `cancel-in-progress` is the ordinary meaning here: a new push supersedes the
# run for the commit it replaced.
concurrency:
group: gateway-proxy-${{ github.ref }}
cancel-in-progress: true
permissions:
contents: read
env:
CARGO_TERM_COLOR: always
jobs:
proxy-integration:
runs-on: ${{ vars.CI_RUNNER || 'ubuntu-latest' }}
# Each of the ~25 arms restarts the gateway, and the idle-timeout arms wait
# out a real timeout. On top of that the suite now builds the shared
# attestation fixture, whose two images compile Rust from a cold docker cache
# on every run -- the cargo cache above does not reach inside a docker build.
timeout-minutes: 45
steps:
- uses: actions/checkout@v5
- name: Install Rust
uses: dtolnay/rust-toolchain@1.92.0
with:
# The container image is built around a static binary.
targets: x86_64-unknown-linux-musl
- name: Install musl toolchain
run: sudo apt-get update && sudo apt-get install -y --no-install-recommends musl-tools
- name: Cache cargo
uses: actions/cache@v4
with:
path: |
~/.cargo/registry
~/.cargo/git
dstack/target
key: gateway-proxy-${{ runner.os }}-${{ hashFiles('dstack/Cargo.lock') }}
restore-keys: gateway-proxy-${{ runner.os }}-
- name: Record kernel capabilities
# The suite adapts to what the kernel offers, so the log needs to say
# what it had: a run that skipped kTLS looks the same as one that
# covered it otherwise.
run: |
echo "kernel: $(uname -r)"
# Without this the suite falls back to a dummy link and skips
# test_accel_status entirely -- including the only positive proof that
# splice engaged. The other two workflows already modprobe it.
sudo modprobe wireguard 2>&1 || echo "no wireguard module available"
sudo modprobe tls 2>&1 || echo "no TLS ULP available"
echo "tls module loaded: $(lsmod | grep -c '^tls ' || true)"
grep -B2 -A3 'gcm(aes)' /proc/crypto | grep -E '^(driver|priority)' \
| paste - - | sort -u || true
- name: Proxy integration tests
working-directory: dstack/gateway/test-run/proxy-e2e
run: ./run-proxy-tests.sh
# No `docker compose logs` here: the suite is driven with `compose run
# --rm`, so its containers are deleted on exit and the script's own trap
# has already run `compose down -v`. Everything worth reading is in the
# bind-mounted work directory, which the suite made host-readable before
# exiting.
- name: Make suite logs readable
if: failure()
working-directory: dstack/gateway/test-run/proxy-e2e
run: |
mkdir -p run
docker run --rm -v "$PWD/run:/r" alpine:latest chmod -R a+rX /r || true
ls -R run || true
- name: Upload logs on failure
if: failure()
uses: actions/upload-artifact@v4
with:
name: gateway-proxy-test-logs
# Only the logs. The work directory also holds each arm's throwaway
# TLS key, and an artifact is the wrong place for key material even
# when it is disposable.
path: dstack/gateway/test-run/proxy-e2e/run/*/logs/
if-no-files-found: ignore
retention-days: 7
- name: Tear down
if: always()
working-directory: dstack/gateway/test-run/proxy-e2e
run: ./run-proxy-tests.sh down