Skip to content

Commit 777749f

Browse files
committed
Add KMS and Gateway upgrade E2E coverage
1 parent 3d88c7b commit 777749f

8 files changed

Lines changed: 724 additions & 100 deletions

File tree

test-suites/full-stack-compose/.env.example

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,15 @@ DSTACK_E2E_IMAGE_STORE=../../../build/images
77
DSTACK_E2E_IMAGE_NAME=dstack-0.6.0
88
DSTACK_E2E_PLATFORM=tdx
99

10-
# App image used inside the only CVM launched by this suite.
10+
# App image used inside the CVMs launched by this suite.
1111
DSTACK_E2E_APP_IMAGE=nginx:alpine
1212

13+
# Released images used by run-upgrade-e2e.sh. The KMS case intentionally pins
14+
# exactly 0.5.7; the script fails before starting the stack if Docker Hub does
15+
# not provide that tag. Gateway 0.5.8 is the default published upgrade source.
16+
DSTACK_E2E_OLD_KMS_IMAGE=dstacktee/dstack-kms:0.5.7
17+
DSTACK_E2E_OLD_GATEWAY_IMAGE=dstacktee/dstack-gateway:0.5.8
18+
1319
# Mock ACME/DNS + externally visible test domain for Gateway SNI.
1420
DSTACK_E2E_BASE_DOMAIN=e2e.test
1521
DSTACK_E2E_PEBBLE_IMAGE=kvin/pebble:latest
@@ -18,6 +24,9 @@ DSTACK_E2E_PEBBLE_IMAGE=kvin/pebble:latest
1824
DSTACK_E2E_VMM_PORT=29080
1925
DSTACK_E2E_AUTH_PORT=28011
2026
DSTACK_E2E_KMS_HOST_PORT=28082
27+
# 0.5.x KMS encodes rpc-domain as a DNS SAN even when it looks like an IP.
28+
# Use a DNS name that resolves to QEMU's 10.0.2.2 host gateway.
29+
DSTACK_E2E_KMS_RPC_DOMAIN=10.0.2.2.nip.io
2130
DSTACK_E2E_GATEWAY_RPC_HOST_PORT=28000
2231
DSTACK_E2E_GATEWAY_ADMIN_HOST_PORT=28001
2332
DSTACK_E2E_GATEWAY_PROXY_HOST_PORT=28443
@@ -48,3 +57,7 @@ DSTACK_E2E_QCNL_CONF=../../dstack/key-provider-build/sgx_default_qcnl.conf
4857
# Runner behaviour.
4958
DSTACK_E2E_CLEAN_START=true
5059
DSTACK_E2E_CLEANUP_AFTER=false
60+
61+
# Upgrade-driver behaviour. Keep the completed stack by default for inspection.
62+
DSTACK_E2E_UPGRADE_CLEAN_STATE=true
63+
DSTACK_E2E_KEEP_STACK=true

test-suites/full-stack-compose/README.md

Lines changed: 68 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,9 @@ docker compose
2020
├─ dstack-vmm # launches real TDX/SNP app CVMs via QEMU
2121
└─ runner
2222
├─ configures Gateway certbot to use mock CF + Pebble
23-
├─ deploys nginx test app CVM with kms_enabled=true + gateway_enabled=true
24-
└─ verifies HTTPS Gateway -> app routing
23+
├─ deploys legacy and lite TDX nginx CVMs
24+
├─ verifies KMS key provisioning and HTTPS Gateway -> app routing
25+
└─ snapshots durable KMS/Gateway state for upgrade assertions
2526
```
2627

2728
It is intended for a **real TDX/SGX host** (or SEV-SNP host after setting the
@@ -62,8 +63,9 @@ a confidential VM for the app.
6263
$EDITOR .env
6364
```
6465

65-
No Gateway image publishing is needed in this architecture: `dstack-gateway` and
66-
`dstack-kms` are run from the local `target/release/` binaries on the host side.
66+
The normal latest-only run needs no published KMS/Gateway image:
67+
`dstack-gateway` and `dstack-kms` run from the local `target/release/` binaries.
68+
The upgrade run additionally pulls its old KMS/Gateway images from Docker Hub.
6769

6870
## Run
6971

@@ -73,13 +75,64 @@ From this directory, run:
7375
DOCKER_BUILDKIT=0 docker compose --env-file .env -f compose.yml up --build --abort-on-container-exit runner
7476
```
7577

78+
On TDX this latest-only path deploys two otherwise identical CVMs. Their v3
79+
manifest requirements force `tdx_measure_acpi_tables=true` (legacy) and
80+
`false` (lite), respectively. Both must reach `boot_progress=done`, which is
81+
after the boot-time `GetAppKey` request, and both must serve nginx through the
82+
Gateway.
83+
84+
## Run the upgrade scenario
85+
86+
The host-side driver is required because the runner container cannot replace
87+
its own KMS/Gateway dependencies:
88+
89+
```bash
90+
DOCKER_BUILDKIT=0 ./run-upgrade-e2e.sh
91+
```
92+
93+
Defaults:
94+
95+
- old KMS: `dstacktee/dstack-kms:0.5.7` (pulled from Docker Hub)
96+
- old Gateway: `dstacktee/dstack-gateway:0.5.8` (pulled from Docker Hub)
97+
- latest KMS, Gateway, VMM, guest image: current checkout/build
98+
99+
The driver deliberately fails during the image-pull preflight if the exact old
100+
image is unavailable. Override images only for intentional matrix runs:
101+
102+
```bash
103+
DSTACK_E2E_OLD_KMS_IMAGE=dstacktee/dstack-kms:0.5.6 \
104+
DSTACK_E2E_OLD_GATEWAY_IMAGE=dstacktee/dstack-gateway:0.5.8 \
105+
./run-upgrade-e2e.sh
106+
```
107+
108+
The upgrade sequence is:
109+
110+
1. Boot a forced-legacy CVM against the old KMS and old Gateway.
111+
2. Record the KMS CA, root k256 public key, and that app's derived environment
112+
encryption public key.
113+
3. Replace only KMS with the current binary while retaining its cert/key dir.
114+
4. Require the recorded identities to be byte-for-byte stable; force-reboot the
115+
legacy CVM so it must provision keys from the new KMS; then boot a forced-lite
116+
CVM from scratch against the new KMS.
117+
5. Flush and record Gateway WaveKV state: node UUID, CVM registrations/IPs,
118+
certbot config, DNS credential, ZT domain, and wildcard cert fingerprint.
119+
6. Start rapid direct HTTP probes to both CVM WireGuard IPs, replace only the
120+
Gateway process, and require **zero failed probe cycles**.
121+
7. Require all recorded Gateway state and the cert fingerprint to survive and
122+
verify both SNI routes through the new Gateway.
123+
124+
The zero-downtime assertion is specifically the CVM WireGuard data plane. The
125+
Gateway process owns the public TLS listener, so a single-node stop/start does
126+
not claim that the external listener itself has zero downtime. External routing
127+
is asserted immediately before and after the upgrade.
128+
76129
For iterative debugging, keep the stack running:
77130

78131
```bash
79132
DOCKER_BUILDKIT=0 docker compose --env-file .env -f compose.yml up --build
80133
```
81134

82-
The runner leaves the app VM running by default for inspection. Set
135+
The runner leaves app VMs running by default for inspection. Set
83136
`DSTACK_E2E_CLEANUP_AFTER=true` to remove suite VMs at the end. Stale VMs whose
84137
names start with `DSTACK_E2E_NAME_PREFIX` are removed at the start when
85138
`DSTACK_E2E_CLEAN_START=true`.
@@ -93,8 +146,16 @@ The runner checks:
93146
3. Gateway obtains a wildcard certificate for `*.DSTACK_E2E_BASE_DOMAIN` from
94147
Pebble using the mock Cloudflare DNS API.
95148
4. VMM API becomes reachable.
96-
5. A real nginx app CVM boots with `kms_enabled=true` and `gateway_enabled=true`.
97-
6. `curl -k` through the Gateway SNI route returns the nginx welcome page.
149+
5. Real nginx app CVMs boot with `kms_enabled=true` and
150+
`gateway_enabled=true`.
151+
6. On TDX, v3 requirements resolve one CVM to legacy attestation and one to
152+
lite; both complete boot-time KMS key provisioning.
153+
7. `curl -k` through each Gateway SNI route returns the nginx welcome page.
154+
8. KMS and Gateway state can be captured in canonical, diffable artifacts.
155+
156+
`run-upgrade-e2e.sh` additionally checks KMS key continuity, Gateway persistence,
157+
old-to-new config/storage compatibility, and zero-failure CVM WireGuard traffic
158+
during the Gateway replacement.
98159

99160
Artifacts are written under `state/work/`.
100161

test-suites/full-stack-compose/compose.yml

Lines changed: 16 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ x-suite-env: &suite-env
1616
DSTACK_E2E_VMM_PORT: ${DSTACK_E2E_VMM_PORT:-29080}
1717
DSTACK_E2E_AUTH_PORT: ${DSTACK_E2E_AUTH_PORT:-28011}
1818
DSTACK_E2E_KMS_HOST_PORT: ${DSTACK_E2E_KMS_HOST_PORT:-28082}
19+
DSTACK_E2E_KMS_RPC_DOMAIN: ${DSTACK_E2E_KMS_RPC_DOMAIN:-10.0.2.2.nip.io}
1920
DSTACK_E2E_GATEWAY_RPC_HOST_PORT: ${DSTACK_E2E_GATEWAY_RPC_HOST_PORT:-28000}
2021
DSTACK_E2E_GATEWAY_ADMIN_HOST_PORT: ${DSTACK_E2E_GATEWAY_ADMIN_HOST_PORT:-28001}
2122
DSTACK_E2E_GATEWAY_PROXY_HOST_PORT: ${DSTACK_E2E_GATEWAY_PROXY_HOST_PORT:-28443}
@@ -37,6 +38,7 @@ x-suite-env: &suite-env
3738
DSTACK_E2E_APP_NAME: ${DSTACK_E2E_APP_NAME:-dstack-e2e-nginx}
3839
DSTACK_E2E_NAME_PREFIX: ${DSTACK_E2E_NAME_PREFIX:-dstack-e2e}
3940
DSTACK_E2E_ALLOW_UNPINNED_IMAGE: ${DSTACK_E2E_ALLOW_UNPINNED_IMAGE:-false}
41+
DSTACK_E2E_PHASE: ${DSTACK_E2E_PHASE:-full}
4042

4143
services:
4244
init-config:
@@ -147,7 +149,11 @@ services:
147149
restart: unless-stopped
148150

149151
kms:
150-
image: dstack-e2e-runtime:local
152+
# The upgrade driver swaps this between the released Docker Hub image and
153+
# the binary built from the current checkout. Keep the executable path
154+
# configurable because the two images install it in different locations.
155+
image: ${DSTACK_E2E_KMS_IMAGE:-dstack-e2e-runtime:local}
156+
entrypoint: ["${DSTACK_E2E_KMS_BIN:-/workspace/target/release/dstack-kms}"]
151157
network_mode: host
152158
depends_on:
153159
init-config:
@@ -159,19 +165,19 @@ services:
159165
- ../../dstack:/workspace:ro
160166
environment:
161167
RUST_LOG: ${DSTACK_E2E_KMS_RUST_LOG:-info,dstack_kms=info}
162-
command:
163-
- bash
164-
- -lc
165-
- exec /workspace/target/release/dstack-kms -c /suite-state/config/kms.toml
168+
command: ["-c", "/suite-state/config/kms.toml"]
166169
healthcheck:
167-
test: ["CMD-SHELL", "curl -kfsS https://127.0.0.1:${DSTACK_E2E_KMS_HOST_PORT:-28082}/metrics >/dev/null"]
170+
test: ["CMD-SHELL", "timeout 3 sh -c \"printf 'GET /prpc/GetMeta?json HTTP/1.1\\r\\nHost: localhost\\r\\nConnection: close\\r\\n\\r\\n' | openssl s_client -quiet -connect 127.0.0.1:${DSTACK_E2E_KMS_HOST_PORT:-28082} 2>/dev/null | grep -q '^HTTP/1.1 200'\""]
168171
interval: 5s
169172
timeout: 3s
170173
retries: 30
171174
restart: unless-stopped
172175

173176
gateway:
174-
image: dstack-e2e-runtime:local
177+
# See kms above. The old image entrypoint is intentionally bypassed so
178+
# both versions consume the exact same config and persistent data dir.
179+
image: ${DSTACK_E2E_GATEWAY_IMAGE:-dstack-e2e-runtime:local}
180+
entrypoint: ["${DSTACK_E2E_GATEWAY_BIN:-/workspace/target/release/dstack-gateway}"]
175181
privileged: true
176182
network_mode: host
177183
depends_on:
@@ -185,12 +191,9 @@ services:
185191
- /lib/modules:/lib/modules:ro
186192
environment:
187193
RUST_LOG: ${DSTACK_E2E_GATEWAY_RUST_LOG:-info,dstack_gateway=debug,certbot=debug}
188-
command:
189-
- bash
190-
- -lc
191-
- exec /workspace/target/release/dstack-gateway -c /suite-state/config/gateway.toml
194+
command: ["-c", "/suite-state/config/gateway.toml"]
192195
healthcheck:
193-
test: ["CMD-SHELL", "curl -kfsS https://127.0.0.1:${DSTACK_E2E_GATEWAY_RPC_HOST_PORT:-28000}/health >/dev/null"]
196+
test: ["CMD-SHELL", "timeout 3 sh -c \"printf 'GET /prpc/Info?json HTTP/1.1\\r\\nHost: localhost\\r\\nConnection: close\\r\\n\\r\\n' | openssl s_client -quiet -connect 127.0.0.1:${DSTACK_E2E_GATEWAY_RPC_HOST_PORT:-28000} 2>/dev/null | grep -q '^HTTP/1.1 200'\""]
194197
interval: 5s
195198
timeout: 3s
196199
retries: 30
@@ -248,7 +251,7 @@ services:
248251
- ./scripts:/suite/scripts:ro
249252
- ../../dstack:/workspace:ro
250253
environment:
251-
DSTACK_E2E_STATE_DIR: /suite-state
254+
<<: *suite-env
252255
DSTACK_E2E_CLEAN_START: ${DSTACK_E2E_CLEAN_START:-true}
253256
DSTACK_E2E_CLEANUP_AFTER: ${DSTACK_E2E_CLEANUP_AFTER:-false}
254257
DSTACK_E2E_APP_VCPU: ${DSTACK_E2E_APP_VCPU:-2}

0 commit comments

Comments
 (0)