diff --git a/scripts/ci/run-ci-tests.sh b/scripts/ci/run-ci-tests.sh index 1d46721d0..262326047 100755 --- a/scripts/ci/run-ci-tests.sh +++ b/scripts/ci/run-ci-tests.sh @@ -367,6 +367,13 @@ run_non_shardable_tests() { echo "Skipping compression/incremental test" fi + # Raw compression fallback image format. + if criu/criu check --feature compress; then + make -C test/others/compression/raw run + else + echo "Skipping compression/raw test" + fi + ./test/zdtm.py run -t zdtm/transition/pid_reuse --pre 2 # start time based pid reuse detection ./test/zdtm.py run -t zdtm/transition/pidfd_store_sk --rpc --pre 2 # pidfd based pid reuse detection diff --git a/test/others/compression/raw/.gitignore b/test/others/compression/raw/.gitignore new file mode 100644 index 000000000..899157d3e --- /dev/null +++ b/test/others/compression/raw/.gitignore @@ -0,0 +1 @@ +dump-raw-*/ diff --git a/test/others/compression/raw/Makefile b/test/others/compression/raw/Makefile new file mode 100644 index 000000000..594edc070 --- /dev/null +++ b/test/others/compression/raw/Makefile @@ -0,0 +1,3 @@ +run: + ./run.sh +.PHONY: run diff --git a/test/others/compression/raw/check_raw_entry.py b/test/others/compression/raw/check_raw_entry.py new file mode 100644 index 000000000..2963e2784 --- /dev/null +++ b/test/others/compression/raw/check_raw_entry.py @@ -0,0 +1,86 @@ +#!/usr/bin/env python3 + +import argparse +import os +import sys + +import pycriu.images + + +PE_PRESENT = 4 +PE_PAYLOAD_ALIGNED = 8 + + +def check_raw_entry(directory, pid): + path = os.path.join(directory, "pagemap-%s.img" % pid) + + with open(path, "rb") as image: + entries = pycriu.images.load(image)["entries"][1:] + + page_size = os.sysconf("SC_PAGE_SIZE") + payload_offset = 0 + found = False + found_lz4 = False + for entry in entries: + flags = int(entry.get("flags", 0)) + if not flags & PE_PRESENT: + continue + if flags & PE_PAYLOAD_ALIGNED: + payload_offset = (payload_offset + page_size - 1) & -page_size + + is_raw_target = ( + int(entry.get("nr_pages", 0)) == 56 + and "compressed_size" not in entry + and "total_compressed_size" not in entry + and "region_pages" not in entry + ) + if is_raw_target: + if not flags & PE_PAYLOAD_ALIGNED: + print("FAIL: all-raw entry lacks PE_PAYLOAD_ALIGNED in %s" % path) + return 1 + if payload_offset % page_size: + print( + "FAIL: all-raw entry starts at unaligned offset %d" + % payload_offset + ) + return 1 + found = True + + compressed_sizes = entry.get("compressed_size") + if compressed_sizes: + block_pages = int(entry.get("region_pages", 0)) or 1 + remaining = int(entry.get("nr_pages", 0)) + for size in compressed_sizes: + pages = min(block_pages, remaining) + if 0 < int(size) < pages * page_size: + found_lz4 = True + remaining -= pages + payload_offset += sum(int(size) for size in compressed_sizes) + else: + payload_offset += int(entry.get("nr_pages", 0)) * page_size + + if not found: + print( + "FAIL: no aligned 56-page all-raw entry without compression metadata " + "in %s" % path + ) + return 1 + if not found_lz4: + print("FAIL: compression test produced no LZ4-compressed block in %s" % path) + return 1 + + return 0 + + +def main(): + parser = argparse.ArgumentParser( + description="Validate raw-fallback pagemap metadata" + ) + parser.add_argument("directory") + parser.add_argument("pid") + args = parser.parse_args() + return check_raw_entry(args.directory, args.pid) + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/test/others/compression/raw/run.sh b/test/others/compression/raw/run.sh new file mode 100755 index 000000000..bbfe2c577 --- /dev/null +++ b/test/others/compression/raw/run.sh @@ -0,0 +1,92 @@ +#!/bin/bash + +# Verify that entries made entirely of compression raw fallbacks use the +# ordinary uncompressed pagemap representation and still restore correctly. + +set -euo pipefail + +# shellcheck source=test/others/env.sh +source ../../env.sh + +CRIU_CMD=("${CRIU}" --no-default-config) +ZDTM_DIR="../../../zdtm/static" +SCRIPT_DIR=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd) +RAW_ENTRY_CHECK="${SCRIPT_DIR}/check_raw_entry.py" +PID="" + +function fail { + echo "FAIL: $*" + exit 1 +} + +function cleanup { + if [ -n "$PID" ] && kill -0 "$PID" 2>/dev/null; then + kill -TERM "$PID" 2>/dev/null || true + for _ in $(seq 1 50); do + kill -0 "$PID" 2>/dev/null || break + sleep 0.1 + done + if kill -0 "$PID" 2>/dev/null; then + kill -KILL "$PID" 2>/dev/null || true + fi + fi + rm -rf dump-raw-* +} + +trap cleanup EXIT + +function start_test { + ( + cd "$ZDTM_DIR" || exit 1 + make compress_pages00.cleanout + make compress_pages00 + make compress_pages00.pid || exit 1 + ) + + PID=$(cat "$ZDTM_DIR/compress_pages00.pid") + kill -0 "$PID" || fail "Test didn't start" +} + +function stop_test { + ( + cd "$ZDTM_DIR" || exit 1 + make compress_pages00.stop + grep PASS compress_pages00.out || exit 1 + ) || fail "$1: memory content verification failed" + PID="" +} + +function assert_raw_entry { + local directory="$1" + + PYTHONPATH="${BASE_DIR}/lib${PYTHONPATH:+:${PYTHONPATH}}" \ + python3 "$RAW_ENTRY_CHECK" "$directory" "$PID" +} + +function run_mode { + local name="$1" + shift + local imgdir="dump-raw-$name" + + rm -rf "$imgdir" + mkdir "$imgdir" + start_test + + "${CRIU_CMD[@]}" dump -D "$imgdir" -o dump.log -t "$PID" -v4 "$@" \ + || fail "$name: dump failed" + assert_raw_entry "$imgdir" || fail "$name: raw-entry metadata check failed" + "${CRIU_CMD[@]}" restore -D "$imgdir" -o restore.log -v4 -d \ + --image-io-mode direct \ + || fail "$name: restore failed" + if grep -q "O_DIRECT enabled on pages fd" "$imgdir/restore.log"; then + grep -q "Restoring delayed VMA I/O with native AIO" \ + "$imgdir/restore.log" || \ + fail "$name: aligned raw ranges did not use direct AIO" + fi + stop_test "$name" +} + +run_mode page --compress +run_mode region --compress-region=64K + +echo "Test PASSED"