mirror of
https://github.com/checkpoint-restore/criu.git
synced 2026-07-17 16:47:50 +00:00
test/others: Cover mixed compression chains
Successive layers in an incremental dump can use different compression modes. Restore must interpret each layer through its own inventory and pagemap metadata instead of applying the final image mode to the parent chain. Add chains with two pre-dumps and a final dump for plain-to-page, page-to-plain, plain-to-region, and region-to-plain transitions. Toggle a page between layers so every image carries real payload, check the exact inventory version and compression settings, and restore the ZDTM workload to verify its memory contents. Run the test in CI when both page compression and memory dirty tracking are available. Assisted-by: Codex:GPT-5 Signed-off-by: Radostin Stoyanov <rstoyanov@fedoraproject.org>
This commit is contained in:
parent
ce933af777
commit
f5fc0ddb56
5 changed files with 327 additions and 0 deletions
|
|
@ -360,6 +360,13 @@ run_non_shardable_tests() {
|
|||
echo "Skipping hugetlb compression tests"
|
||||
fi
|
||||
|
||||
# Incremental compression parent chains.
|
||||
if criu/criu check --feature compress && criu/criu check --feature mem_dirty_track; then
|
||||
make -C test/others/compression/incremental run
|
||||
else
|
||||
echo "Skipping compression/incremental 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
|
||||
|
||||
|
|
|
|||
1
test/others/compression/incremental/.gitignore
vendored
Normal file
1
test/others/compression/incremental/.gitignore
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
dump-*/
|
||||
3
test/others/compression/incremental/Makefile
Normal file
3
test/others/compression/incremental/Makefile
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
run:
|
||||
./run.sh
|
||||
.PHONY: run
|
||||
126
test/others/compression/incremental/image_tool.py
Normal file
126
test/others/compression/incremental/image_tool.py
Normal file
|
|
@ -0,0 +1,126 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
import argparse
|
||||
import os
|
||||
import sys
|
||||
|
||||
import pycriu.images
|
||||
|
||||
|
||||
PE_PRESENT = 4
|
||||
EXPECTED_REGION_SIZE = 64 * 1024
|
||||
|
||||
|
||||
def inventory_compression(directory, mode, version):
|
||||
expected_mode = {"plain": 0, "page": 1, "region": 2}[mode]
|
||||
expected_version = int(version)
|
||||
|
||||
with open(os.path.join(directory, "inventory.img"), "rb") as image:
|
||||
inventory = pycriu.images.load(image)["entries"][0]
|
||||
|
||||
if inventory["img_version"] != expected_version:
|
||||
print(
|
||||
"FAIL: %s inventory version is %s, expected %s"
|
||||
% (directory, inventory["img_version"], expected_version)
|
||||
)
|
||||
return 1
|
||||
if int(inventory.get("compress", 0)) != expected_mode:
|
||||
print(
|
||||
"FAIL: %s inventory compression mode is %s, expected %s"
|
||||
% (directory, inventory.get("compress"), expected_mode)
|
||||
)
|
||||
return 1
|
||||
|
||||
region_size = inventory.get("compress_region_size")
|
||||
if expected_mode == 2:
|
||||
if int(region_size or 0) != EXPECTED_REGION_SIZE:
|
||||
print(
|
||||
"FAIL: %s inventory region size is %s, expected 65536"
|
||||
% (directory, region_size)
|
||||
)
|
||||
return 1
|
||||
elif region_size is not None:
|
||||
print(
|
||||
"FAIL: %s has unexpected compression region size %s"
|
||||
% (directory, region_size)
|
||||
)
|
||||
return 1
|
||||
|
||||
return 0
|
||||
|
||||
|
||||
def present_payload(directory, pid):
|
||||
path = os.path.join(directory, "pagemap-%s.img" % pid)
|
||||
with open(path, "rb") as image:
|
||||
entries = pycriu.images.load(image)["entries"][1:]
|
||||
|
||||
if not any(int(entry.get("flags", 0)) & PE_PRESENT for entry in entries):
|
||||
print("FAIL: %s contains no present page payload" % path)
|
||||
return 1
|
||||
|
||||
return 0
|
||||
|
||||
|
||||
def toggle_page(directory, pid, address_file):
|
||||
path = os.path.join(directory, "pagemap-%s.img" % pid)
|
||||
if os.path.exists(address_file):
|
||||
with open(address_file) as saved:
|
||||
address = int(saved.read().strip(), 16)
|
||||
else:
|
||||
with open(path, "rb") as image:
|
||||
entries = pycriu.images.load(image)["entries"][1:]
|
||||
present = [
|
||||
entry
|
||||
for entry in entries
|
||||
if int(entry.get("flags", 0)) & PE_PRESENT
|
||||
]
|
||||
if not present:
|
||||
print("FAIL: no present mapping available to modify in %s" % path)
|
||||
return 1
|
||||
entry = max(present, key=lambda item: int(item.get("nr_pages", 0)))
|
||||
address = int(entry["vaddr"])
|
||||
with open(address_file, "w") as saved:
|
||||
saved.write("%x\n" % address)
|
||||
|
||||
with open("/proc/%s/mem" % pid, "r+b", buffering=0) as memory:
|
||||
value = os.pread(memory.fileno(), 1, address)
|
||||
if len(value) != 1:
|
||||
raise RuntimeError("short read from workload memory")
|
||||
if os.pwrite(memory.fileno(), bytes([value[0] ^ 0xFF]), address) != 1:
|
||||
raise RuntimeError("short write to workload memory")
|
||||
|
||||
return 0
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Inspect incremental compression images and toggle test memory"
|
||||
)
|
||||
subparsers = parser.add_subparsers(dest="command")
|
||||
|
||||
inventory = subparsers.add_parser("inventory-compression")
|
||||
inventory.add_argument("directory")
|
||||
inventory.add_argument("mode", choices=("plain", "page", "region"))
|
||||
inventory.add_argument("version", type=int)
|
||||
|
||||
payload = subparsers.add_parser("present-payload")
|
||||
payload.add_argument("directory")
|
||||
payload.add_argument("pid")
|
||||
|
||||
toggle = subparsers.add_parser("toggle-page")
|
||||
toggle.add_argument("directory")
|
||||
toggle.add_argument("pid")
|
||||
toggle.add_argument("address_file")
|
||||
|
||||
args = parser.parse_args()
|
||||
if args.command is None:
|
||||
parser.error("a command is required")
|
||||
if args.command == "inventory-compression":
|
||||
return inventory_compression(args.directory, args.mode, args.version)
|
||||
if args.command == "present-payload":
|
||||
return present_payload(args.directory, args.pid)
|
||||
return toggle_page(args.directory, args.pid, args.address_file)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
190
test/others/compression/incremental/run.sh
Executable file
190
test/others/compression/incremental/run.sh
Executable file
|
|
@ -0,0 +1,190 @@
|
|||
#!/bin/bash
|
||||
|
||||
# Test incremental checkpoint chains that change compression mode by layer.
|
||||
#
|
||||
# Exercise both ways of mixing compressed and uncompressed iterative
|
||||
# checkpoint images. Readers must use the compression metadata from their
|
||||
# own pagemap entries, not from the top image inventory.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# shellcheck source=test/others/env.sh
|
||||
source ../../env.sh
|
||||
|
||||
function fail {
|
||||
echo "FAIL: $*"
|
||||
exit 1
|
||||
}
|
||||
|
||||
set -x
|
||||
|
||||
CRIU_CMD=("${CRIU}" --no-default-config)
|
||||
ZDTM_DIR="../../../zdtm/static"
|
||||
SCRIPT_DIR=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)
|
||||
IMAGE_TOOL="${SCRIPT_DIR}/image_tool.py"
|
||||
PID=""
|
||||
|
||||
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-*
|
||||
}
|
||||
|
||||
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 run_image_tool {
|
||||
PYTHONPATH="${BASE_DIR}/lib${PYTHONPATH:+:${PYTHONPATH}}" \
|
||||
python3 "$IMAGE_TOOL" "$@"
|
||||
}
|
||||
|
||||
function assert_inventory_compression {
|
||||
local directory="$1"
|
||||
local expected_mode="$2"
|
||||
local expected_version="$3"
|
||||
|
||||
run_image_tool inventory-compression \
|
||||
"$directory" "$expected_mode" "$expected_version"
|
||||
}
|
||||
|
||||
function assert_present_payload {
|
||||
local directory="$1"
|
||||
|
||||
run_image_tool present-payload "$directory" "$PID"
|
||||
}
|
||||
|
||||
function toggle_test_page {
|
||||
local directory="$1"
|
||||
local address_file="$2"
|
||||
|
||||
# Pick the largest present mapping from the preceding image. Toggling its
|
||||
# first byte after each pre-dump makes the next layer contain real payload;
|
||||
# the second toggle restores the byte expected by the ZDTM workload.
|
||||
run_image_tool toggle-page "$directory" "$PID" "$address_file"
|
||||
}
|
||||
|
||||
function validate_compression_mode {
|
||||
local chain_name="$1"
|
||||
local stage="$2"
|
||||
local mode="$3"
|
||||
|
||||
case "$mode" in
|
||||
plain | page | region) ;;
|
||||
*) fail "$chain_name: unknown $stage compression mode $mode" ;;
|
||||
esac
|
||||
}
|
||||
|
||||
function create_checkpoint_layer {
|
||||
local chain_name="$1"
|
||||
local stage="$2"
|
||||
local operation="$3"
|
||||
local directory="$4"
|
||||
local previous="$5"
|
||||
local compression_mode="$6"
|
||||
local expected_version="$7"
|
||||
local args=("$operation" -D "$directory" -o dump.log -t "$PID" -v4)
|
||||
|
||||
args+=(--track-mem)
|
||||
if [ "$operation" = "pre-dump" ]; then
|
||||
args+=(-R)
|
||||
fi
|
||||
if [ -n "$previous" ]; then
|
||||
args+=(--prev-images-dir="$previous")
|
||||
fi
|
||||
|
||||
case "$compression_mode" in
|
||||
page) args+=(--compress) ;;
|
||||
region) args+=(--compress-region=64K) ;;
|
||||
plain) ;;
|
||||
esac
|
||||
|
||||
echo "=== $chain_name: $stage ==="
|
||||
mkdir "$directory"
|
||||
"${CRIU_CMD[@]}" "${args[@]}" || fail "$chain_name: $stage failed"
|
||||
assert_present_payload "$directory" || \
|
||||
fail "$chain_name: $stage contains no payload"
|
||||
assert_inventory_compression \
|
||||
"$directory" "$compression_mode" "$expected_version" || \
|
||||
fail "$chain_name: $stage inventory compression state is invalid"
|
||||
}
|
||||
|
||||
function run_chain {
|
||||
local name="$1"
|
||||
local predump_compress="$2"
|
||||
local dump_compress="$3"
|
||||
local imgdir="dump-$name"
|
||||
local parent_version=2
|
||||
local final_version
|
||||
|
||||
validate_compression_mode "$name" "pre-dump" "$predump_compress"
|
||||
validate_compression_mode "$name" "final dump" "$dump_compress"
|
||||
|
||||
if [ "$predump_compress" != "plain" ]; then
|
||||
parent_version=3
|
||||
fi
|
||||
|
||||
final_version=$parent_version
|
||||
if [ "$dump_compress" != "plain" ]; then
|
||||
final_version=3
|
||||
fi
|
||||
|
||||
rm -rf "$imgdir"
|
||||
mkdir "$imgdir"
|
||||
|
||||
start_test
|
||||
|
||||
create_checkpoint_layer "$name" "pre-dump 1" pre-dump \
|
||||
"$imgdir/1/" "" "$predump_compress" "$parent_version"
|
||||
toggle_test_page "$imgdir/1/" "$imgdir/toggle-address" \
|
||||
|| fail "$name: unable to modify workload"
|
||||
|
||||
create_checkpoint_layer "$name" "pre-dump 2" pre-dump \
|
||||
"$imgdir/2/" "../1/" "$predump_compress" "$parent_version"
|
||||
toggle_test_page "$imgdir/2/" "$imgdir/toggle-address" \
|
||||
|| fail "$name: unable to restore workload byte"
|
||||
|
||||
create_checkpoint_layer "$name" "final dump" dump \
|
||||
"$imgdir/3/" "../2/" "$dump_compress" "$final_version"
|
||||
|
||||
echo "=== $name: restore ==="
|
||||
"${CRIU_CMD[@]}" restore -D "$imgdir/3/" -o restore.log -v4 -d \
|
||||
|| fail "$name: restore failed"
|
||||
|
||||
stop_test "$name"
|
||||
rm -rf "$imgdir"
|
||||
}
|
||||
|
||||
run_chain "plain-parents-compressed-final" plain page
|
||||
run_chain "compressed-parents-plain-final" page plain
|
||||
run_chain "plain-parents-region-final" plain region
|
||||
run_chain "region-parents-plain-final" region plain
|
||||
|
||||
echo "Test PASSED"
|
||||
Loading…
Add table
Add a link
Reference in a new issue