Files
fengcone 1ea4e38580 feat(kubernetes): add QEMU VMState pause and resume (#1584)
* feat(kubernetes): add QEMU VMState pause and resume

Add opt-in process-level pause and resume support for QEMU running inside
runc sandboxes.

- capture the outer rootfs and writable qcow2 overlay in the rootfs image
- export guest memory, vCPU, and device state through QMP migration
- package compressed VMState as a separate OCI image
- restore rootfs and VMState exclusively through immutable image digests
- support pooled BatchSandbox pause, detachment, and standalone restoration
- add authenticated Registry credentials for snapshot push and restore pull
- add QEMU checkpoint, recovery, VMState loading, and compatibility contracts
- simplify the E2E guest to expose mmap and disk markers through HTTP
- verify Guest memory, qcow2, outer rootfs, boot ID, and counter continuity
- add private samples, operational documentation, and Kind/KVM validation
- preserve existing rootfs-only pause and resume behavior

* docs(kubernetes): clarify QEMU VMState design and operations

* fix(kubernetes): preserve HostPID and SOURCE_POD_UID in snapshot jobs

Two omissions introduced during the QEMU VMState cherry-pick:

- applyImageCommitterPodTemplate overwrote HostPID with the overlay default (false), causing QEMU commit jobs to lose host PID access required for nerdctl exec into the QEMU container.
- ensureUnpauseJob accepted sourcePodUID but did not inject it into the unpause job env, breaking the SOURCE_POD_UID security contract that lets image-committer match containers by pod UID on unpause.

* fix(kubernetes): address snapshot review findings

* fix(kubernetes): resolve golint issues in snapshot code

* fix(kubernetes): harden QEMU restore init container security and storage

* fix(kubernetes): mark ResumeFailed when pod failures race the Resuming phase

Under informer lag, pod failures observed during a resume can be
classified while the cached BatchSandbox phase still shows the pre-pause
steady phase, so the failure path skipped the ResumeFailed condition and
only reported PodFailed. Detect an in-flight resume via generation and
spec in the steady failure branch and set ResumeFailed as well.

Also make the e2e ResumeFailed assertion poll instead of checking once.

* test(kubernetes): dump cluster diagnostics when pause-resume e2e specs fail

Flaky pause/resume CI failures left no controller or object state behind
because the kind cluster is torn down right after the suite. Dump
BatchSandbox/SandboxSnapshot/Job/Pod state, events, and controller logs
to GinkgoWriter whenever a spec fails.

* ci(e2e): wipe stale mavenLocal SDK artifacts before Java E2E

The self-hosted runner's shared ~/.m2 can contain a higher-versioned
com.alibaba.opensandbox jar published by another job, and tests/java
resolves 'latest.integration' from mavenLocal, so compilation fails
with missing Builder methods (e.g. reconcileInterval). Remove the group
directory before publishing so tests resolve the freshly built SDK.

---------

Co-authored-by: Sky <yutian.taoyt@alibaba-inc.com>
2026-08-21 13:26:08 +08:00

93 lines
3.0 KiB
Bash

#!/bin/bash
# Copyright 2025 Alibaba Group Holding Ltd.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
set -euxo pipefail
TAG=${TAG:-latest}
RUN_CODE_INTERPRETER_E2E=${RUN_CODE_INTERPRETER_E2E:-false}
REPO_ROOT="$(cd "$(dirname "$0")/.." && pwd)"
SERVER_PID=""
source "${REPO_ROOT}/scripts/credential-vault-e2e-target.sh"
cleanup_server() {
if [ -n "${SERVER_PID}" ]; then
kill "${SERVER_PID}" 2>/dev/null || true
wait "${SERVER_PID}" 2>/dev/null || true
fi
}
cleanup() {
cleanup_server
cleanup_credential_vault_e2e_target
}
trap cleanup EXIT
# build execd image locally (context must include internal/)
docker build -f components/execd/Dockerfile -t opensandbox/execd:local "${REPO_ROOT}"
# prepare required images from registry
docker pull opensandbox/code-interpreter:${TAG}
echo "-------- Eval test images --------"
docker images
# prepare hostpath volume for e2e test
mkdir -p /tmp/opensandbox-e2e/host-volume-test
mkdir -p /tmp/opensandbox-e2e/logs
echo "opensandbox-e2e-marker" > /tmp/opensandbox-e2e/host-volume-test/marker.txt
chmod -R 755 /tmp/opensandbox-e2e
# prepare Docker named volume for pvc e2e test
docker volume rm opensandbox-e2e-pvc-test 2>/dev/null || true
docker volume create opensandbox-e2e-pvc-test
# seed the named volume with a marker file and subpath test data via a temporary container
docker run --rm -v opensandbox-e2e-pvc-test:/data alpine sh -c "\
echo 'pvc-marker-data' > /data/marker.txt && \
mkdir -p /data/datasets/train && \
echo 'pvc-subpath-marker' > /data/datasets/train/marker.txt"
echo "-------- JAVA E2E test logs for execd --------" > /tmp/opensandbox-e2e/logs/execd.log
export OPENSANDBOX_CREDENTIAL_VAULT_E2E_SANDBOX_IMAGE="${OPENSANDBOX_CREDENTIAL_VAULT_E2E_SANDBOX_IMAGE:-opensandbox/code-interpreter:${TAG}}"
setup_credential_vault_e2e_target
# setup server
cd server
export OPENSANDBOX_INSECURE_SERVER=YES
uv sync
uv run python -m opensandbox_server.main > server.log 2>&1 &
SERVER_PID=$!
cd ..
# wait for server
sleep 10
# Wipe stale locally-published SDK artifacts from the shared runner's mavenLocal:
# tests/java resolves 'latest.integration', and a stale higher-version jar published
# by another job would otherwise win over the version built from this checkout.
rm -rf "${HOME:?HOME must be set}/.m2/repository/com/alibaba/opensandbox"
cd sdks/sandbox/kotlin
./gradlew clean publishToMavenLocal --no-build-cache
cd ../../../
# run Java e2e
cd tests/java
if [ "${RUN_CODE_INTERPRETER_E2E}" = "true" ]; then
./gradlew test
else
./gradlew test -PskipCodeInterpreterE2E=true
fi