crun-vm/tests/env.sh

425 lines
11 KiB
Bash
Executable File

#!/bin/bash
# SPDX-License-Identifier: GPL-2.0-or-later
set -o errexit -o pipefail -o nounset
start_time="$( date +%s%N )"
env_image=quay.io/crun-vm/test-env:latest
container_name=crun-vm-test-env
declare -A TEST_IMAGES
TEST_IMAGES=(
[fedora]=quay.io/containerdisks/fedora:40 # uses cloud-init
[coreos]=quay.io/crun-vm/example-fedora-coreos:40 # uses Ignition
[fedora-bootc]=quay.io/crun-vm/example-fedora-bootc:40 # bootable container
)
declare -A TEST_IMAGES_DEFAULT_USER
TEST_IMAGES_DEFAULT_USER=(
[fedora]=fedora
[coreos]=core
[fedora-bootc]=fedora
)
declare -A TEST_IMAGES_DEFAULT_USER_HOME
TEST_IMAGES_DEFAULT_USER_HOME=(
[fedora]=/home/fedora
[coreos]=/var/home/core
[fedora-bootc]=/var/home/cloud-user
)
__bad_usage() {
>&2 echo -n "\
Usage: $0 <command> [<args...>]
build
$0 run <engine> <test_scripts...>
$0 start
$0 stop
COMMANDS
build
Build the test env VM container image.
start
Start the test env VM.
restart
Stop the test env VM if running, then start it.
stop
Stop the test env VM if running.
run <engine> <test_script>
run <engine> all
Run a test script in the test env VM under the given engine. <engine> must
be one of 'podman', 'rootful-podman', 'docker', or 'all'.
ssh
SSH into the test env VM for debugging.
"
exit 2
}
# Usage: __elapsed
__elapsed() {
local delta
delta=$(( $( date +%s%N ) - start_time ))
printf '%d.%09d' "$(( delta / 10**9 ))" "$(( delta % 10**9 ))"
}
# Usage: __small_log_without_time <color> <format> <args...>
__small_log_without_time() {
# shellcheck disable=SC2059
>&2 printf "\033[%sm--- %s\033[0m\n" \
"$1" "$( printf "${@:2}" )"
}
# Usage: __log <color> <format> <args...>
__small_log() {
# shellcheck disable=SC2059
>&2 printf "\033[%sm--- [%6.1f] %s\033[0m\n" \
"$1" "$( __elapsed )" "$( printf "${@:2}" )"
}
# Usage: __big_log <color> <format> <args...>
__big_log() {
local text term_cols sep_len
text="$( printf "${@:2}" )"
term_cols="$( tput cols 2> /dev/null )" || term_cols=80
sep_len="$(( term_cols - ${#text} - 16 ))"
>&2 printf "\033[%sm--- [%6.1f] %s " "$1" "$( __elapsed )" "${text}"
>&2 printf '%*s\033[0m\n' "$(( sep_len < 0 ? 0 : sep_len ))" '' | tr ' ' -
}
__log_without_time_and_run() {
__small_log_without_time 36 '$ %s' "$*"
"$@"
}
__log_and_run() {
__small_log 36 '$ %s' "$*"
"$@"
}
__rel() {
realpath -s --relative-to=. "$1"
}
__build_runtime() {
__big_log 33 'Building crun-vm...'
__log_and_run make -C "$repo_root"
runtime=$repo_root/out/crun-vm
}
__extra_cleanup() { :; }
repo_root=$( readlink -e "$( dirname "$0" )/.." )
cd "$repo_root"
temp_dir=$( mktemp -d )
trap '__extra_cleanup; rm -fr "$temp_dir"' EXIT
export RUST_BACKTRACE=1 RUST_LIB_BACKTRACE=1
case "${1:-}" in
build)
if (( $# != 1 )); then
__bad_usage
fi
__build_runtime
__big_log 33 'Building test env image...'
# build disk image
packages=(
bash
cloud-init
coreutils
crun
crun-krun
docker
genisoimage
grep
htop
libselinux-devel
libvirt-client
libvirt-daemon-driver-qemu
libvirt-daemon-log
lsof
openssh-clients
podman
qemu-img
qemu-system-x86-core
shadow-utils
util-linux
virtiofsd
)
packages_joined=$( printf ",%s" "${packages[@]}" )
packages_joined=${packages_joined:1}
daemon_json='{ "runtimes": { "crun-vm": { "path": "/home/fedora/bin/crun-vm" } } }'
commands=(
# generate an ssh keypair for users fedora and root so crun-vm
# containers get a predictable keypair
'ssh-keygen -q -f /root/.ssh/id_rsa -N ""'
"mkdir -p /etc/docker && echo ${daemon_json@Q} > /etc/docker/daemon.json"
)
__log_and_run virt-builder \
"fedora-${CRUN_VM_TEST_ENV_FEDORA_VERSION:-40}" \
--smp "$( nproc )" \
--memsize 4096 \
--format qcow2 \
--output "$temp_dir/image.qcow2" \
--size 50G \
--root-password password:root \
--install "$packages_joined" \
"${commands[@]/#/--run-command=}"
# reduce image file size
__log_and_run virt-sparsify --in-place "$temp_dir/image.qcow2"
__log_and_run qemu-img convert -f qcow2 -O qcow2 \
"$temp_dir/image.qcow2" "$temp_dir/image-small.qcow2"
# package new image file
__log_and_run "$( __rel "$repo_root/util/package-vm-image.sh" )" \
"$temp_dir/image-small.qcow2" \
"$env_image"
__big_log 33 'Done.'
;;
start)
if (( $# != 1 )); then
__bad_usage
fi
if podman container exists "$container_name"; then
>&2 echo "Already started."
exit 0
fi
__build_runtime
# launch VM
__log_and_run podman run \
--name "$container_name" \
--pull never \
--runtime "$runtime" \
--memory 8g \
--rm -dit \
-v "$temp_dir":/home/fedora/images:z \
-v "$repo_root/out":/home/fedora/bin:z \
"$env_image"
# shellcheck disable=SC2317
__extra_cleanup() {
__log_and_run podman stop --time 0 "$container_name"
}
__exec() {
__log_and_run podman exec "$container_name" --as fedora "$@"
}
chmod a+rx "$temp_dir" # so user "fedora" in guest can access it
__exec sudo cp /root/.ssh/id_rsa /root/.ssh/id_rsa.pub .ssh/
__exec sudo chown fedora:fedora . .ssh/id_rsa .ssh/id_rsa.pub
# load test images onto VM
for image in "${TEST_IMAGES[@]}"; do
__log_and_run podman pull "$image"
__log_and_run podman save "$image" -o "$temp_dir/image.tar"
__exec cp /home/fedora/images/image.tar image.tar
__exec podman load -i image.tar
__exec sudo podman load -i image.tar
__exec sudo docker load -i image.tar
__exec rm image.tar
rm "$temp_dir/image.tar"
done
__extra_cleanup() { :; }
;;
restart)
"$0" stop
"$0" start
;;
stop)
if (( $# != 1 )); then
__bad_usage
fi
__log_and_run podman stop --ignore "$container_name"
__log_and_run podman wait --ignore "$container_name"
;;
run)
if (( $# < 3 )); then
__bad_usage
fi
case "$2" in
podman|rootful-podman|docker)
engines=( "$2" )
;;
all)
engines=( podman rootful-podman docker )
;;
*)
__bad_usage
;;
esac
if (( $# == 3 )) && [[ "$3" == all ]]; then
mapfile -d '' -t tests < <( find "$repo_root/tests/t" -type f -print0 | sort -z )
else
tests=( "${@:3}" )
fi
if ! podman container exists "$container_name"; then
>&2 echo "The test environment VM isn't running. Start it with:"
>&2 echo " \$ $0 start"
exit 1
fi
__build_runtime
for t in "${tests[@]}"; do
for engine in "${engines[@]}"; do
__big_log 33 'Running test %s under %s...' "$( __rel "$t" )" "$engine"
case "$engine" in
podman)
engine_cmd=( podman )
runtime_in_env=/home/fedora/bin/crun-vm
;;
rootful-podman)
engine_cmd=( sudo podman )
runtime_in_env=/home/fedora/bin/crun-vm
;;
docker)
engine_cmd=( sudo docker )
runtime_in_env=crun-vm
;;
esac
# generate random label for containers created by test script
label=$( mktemp --dry-run | xargs basename )
# shellcheck disable=SC2317
__engine() {
if [[ "$1" == run ]]; then
__log_and_run "${engine_cmd[@]}" run \
--runtime "$runtime_in_env" \
--pull never \
--label "$label" \
"${@:2}"
else
__log_and_run "${engine_cmd[@]}" "$@"
fi
}
__exec() {
podman exec -i "$container_name" --as fedora "$@"
}
# shellcheck disable=SC2088
__exec mkdir "$label.temp" "$label.util"
# copy util scripts
for file in $repo_root/util/*; do
contents=$( cat "$file" )
path_in_vm=$label.util/$( basename "$file" )
__exec "echo ${contents@Q} > $path_in_vm && chmod +x $path_in_vm"
done
# run test
full_script="\
set -o errexit -o pipefail -o nounset
$(
declare -p \
TEST_IMAGES TEST_IMAGES_DEFAULT_USER TEST_IMAGES_DEFAULT_USER_HOME \
engine_cmd runtime_in_env label start_time
)
$( declare -f __elapsed __engine __log_and_run __small_log )
__skip() {
exit 0
}
__log() {
__small_log 36 \"\$@\"
}
TEMP_DIR=~/$label.temp
UTIL_DIR=~/$label.util
TEST_ID=$label
ENGINE=$engine
export RUST_BACKTRACE=1 RUST_LIB_BACKTRACE=1
$( cat "$t" )\
"
exit_code=0
__exec <<< "$full_script" || exit_code=$?
# remove any leftover containers
__small_log 36 'Cleaning up...'
full_script="\
set -o errexit -o pipefail -o nounset
${engine_cmd[*]} ps --filter label=$label --format '{{.Names}}' |
xargs --no-run-if-empty ${engine_cmd[*]} stop --time 0 \
>/dev/null 2>&1
${engine_cmd[*]} ps --filter label=$label --format '{{.Names}}' --all |
xargs --no-run-if-empty ${engine_cmd[*]} rm --force \
>/dev/null 2>&1 \
|| true # avoid 'removal already in progress' docker errors
${engine_cmd[*]} ps --filter label=$label --format '{{.Names}}' --all |
xargs --no-run-if-empty false # fail if containers still exist
sudo rm -fr $label.temp $label.util
"
__exec <<< "$full_script"
# report test result
if (( exit_code == 0 )); then
__small_log 36 'Test succeeded.'
else
__small_log 36 'Test failed.'
__big_log 31 'A test failed.'
exit "$exit_code"
fi
done
done
__big_log 32 'All tests succeeded.'
;;
ssh)
__log_and_run podman exec -it "$container_name" --as fedora "${@:2}"
;;
*)
__bad_usage
;;
esac