Files
snapd/tests/lib/nested.sh
Valentin David b1dc3c675d tests: properly build snapd snap (#14141)
* tests: properly build snapd snap

Now we build also the test version of snapd snap in `snap-builds`
workflow job. We copy this into the spread tests. And we use that
snap, which we only instrument instead of copying the snapd deb build.

If the snap is not available, then we build it in spread. On CI, this
happens on arm since the workflow does not build it. It will also
happen when triggering test manually.

* tests: couple of small improvements to test syntax, move WORK_DIR into script scope, use PWD instead of dot notation

---------

Co-authored-by: Philip Meulengracht <the_meulengracht@hotmail.com>
2024-07-03 13:36:31 +02:00

1733 lines
59 KiB
Bash
Executable File

#!/bin/bash
: "${NESTED_WORK_DIR:=/tmp/work-dir}"
: "${NESTED_IMAGES_DIR:=${NESTED_WORK_DIR}/images}"
: "${NESTED_RUNTIME_DIR:=${NESTED_WORK_DIR}/runtime}"
: "${NESTED_ASSETS_DIR:=${NESTED_WORK_DIR}/assets}"
: "${NESTED_LOGS_DIR:=${NESTED_WORK_DIR}/logs}"
: "${NESTED_ARCHITECTURE:=amd64}"
: "${NESTED_VM:=nested-vm}"
: "${NESTED_SSH_PORT:=8022}"
: "${NESTED_MON_PORT:=8888}"
: "${NESTED_CUSTOM_MODEL:=}"
: "${NESTED_CUSTOM_AUTO_IMPORT_ASSERTION:=}"
: "${NESTED_FAKESTORE_BLOB_DIR:=${NESTED_WORK_DIR}/fakestore/blobs}"
: "${NESTED_SIGN_SNAPS_FAKESTORE:=false}"
: "${NESTED_FAKESTORE_SNAP_DECL_PC_GADGET:=}"
: "${NESTED_UBUNTU_IMAGE_SNAPPY_FORCE_SAS_URL:=}"
: "${NESTED_UBUNTU_IMAGE_PRESEED_KEY:=}"
: "${NESTED_DISK_PHYSICAL_BLOCK_SIZE:=512}"
: "${NESTED_DISK_LOGICAL_BLOCK_SIZE:=512}"
nested_wait_for_ssh() {
local retry=${1:-800}
local wait=${2:-1}
until remote.exec "true" &>/dev/null; do
if [ "$retry" -le 0 ]; then
return 1
fi
retry=$(( retry - 1 ))
sleep "$wait"
done
}
nested_wait_for_no_ssh() {
local retry=${1:-200}
local wait=${2:-1}
while remote.exec "true" &>/dev/null; do
if [ "$retry" -le 0 ]; then
return 1
fi
retry=$(( retry - 1 ))
sleep "$wait"
done
}
nested_wait_vm_ready() {
echo "Waiting the vm is ready to be used"
local retry=${1:-120}
local serial_log="$NESTED_LOGS_DIR"/serial.log
while true; do
# Check the timeout is reached
if [ "$retry" -le 0 ]; then
echo "Timed out waiting for vm ready. Aborting!"
return 1
fi
retry=$(( retry - 1 ))
# Check the vm is active
if ! systemctl is-active "$NESTED_VM"; then
echo "Unit $NESTED_VM is not active. Aborting!"
return 1
fi
# Check if ssh connection can be established, and return if it is possible
if nested_wait_for_ssh 1 1; then
echo "SSH connection ready"
return
fi
# Check no infinite loops during boot
if nested_is_core_ge 20; then
test "$(grep -c -E "Command line:.*snapd_recovery_mode=install" "$serial_log")" -le 1
test "$(grep -c -E "Command line:.*snapd_recovery_mode=run" "$serial_log")" -le 1
else
test "$(grep -c -E "Command line:.*BOOT_IMAGE=\(loop\)/kernel.img" "$serial_log")" -le 1
fi
sleep 3
done
nested_check_unit_stays_active "$NESTED_VM" 2 1
}
nested_wait_for_snap_command() {
# In this function the remote retry command cannot be used because it could
# be executed before the tool is deployed.
local retry=${1:-200}
local wait=${2:-1}
while ! remote.exec "command -v snap" &>/dev/null; do
if [ "$retry" -le 0 ]; then
echo "Timed out waiting for command 'command -v snap' to success. Aborting!"
return 1
fi
retry=$(( retry - 1 ))
sleep "$wait"
done
}
nested_check_unit_stays_active() {
local nested_unit="${1:-$NESTED_VM}"
local retry=${2:-5}
local wait=${3:-1}
while [ "$retry" -ge 0 ]; do
retry=$(( retry - 1 ))
if ! systemctl is-active "$nested_unit"; then
echo "Unit $nested_unit is not active. Aborting!"
return 1
fi
sleep "$wait"
done
}
nested_get_boot_id() {
remote.exec "cat /proc/sys/kernel/random/boot_id"
}
nested_wait_for_reboot() {
local initial_boot_id="$1"
local last_boot_id="$initial_boot_id"
local retry=150
local wait=5
while [ $retry -ge 0 ]; do
retry=$(( retry - 1 ))
# The get_boot_id could fail because the connection is broken due to the reboot
last_boot_id="$(nested_get_boot_id)" || true
if [[ "$last_boot_id" =~ .*-.*-.*-.*-.* ]] && [ "$last_boot_id" != "$initial_boot_id" ]; then
break
fi
sleep "$wait"
done
[ "$last_boot_id" != "$initial_boot_id" ]
}
nested_uc20_transition_to_system_mode() {
local recovery_system="$1"
local mode="$2"
if nested_is_core_le 18; then
echo "Transition can be done just on uc20+ systems, exiting..."
exit 1
fi
local current_boot_id
current_boot_id=$(nested_get_boot_id)
remote.exec "sudo snap reboot --$mode $recovery_system"
nested_wait_for_reboot "$current_boot_id"
# verify we are now in the requested mode
if ! remote.exec "cat /proc/cmdline" | MATCH "snapd_recovery_mode=$mode"; then
return 1
fi
# Copy tools to be used on tests
nested_prepare_tools
}
nested_prepare_ssh() {
if nested_is_core_ge 24; then
remote.exec "sudo useradd --uid 12345 --create-home --extrausers test"
remote.exec "sudo useradd --create-home --extrausers external"
else
remote.exec "sudo adduser --uid 12345 --extrausers --quiet --disabled-password --gecos '' test"
remote.exec "sudo adduser --extrausers --quiet --disabled-password --gecos '' external"
fi
remote.exec "echo test:ubuntu123 | sudo chpasswd"
remote.exec "echo 'test ALL=(ALL) NOPASSWD:ALL' | sudo tee /etc/sudoers.d/create-user-test"
# Check we can connect with the new test user and make sudo
remote.exec --user test --pass ubuntu123 "sudo true"
remote.exec "echo external:ubuntu123 | sudo chpasswd"
remote.exec "echo 'external ALL=(ALL) NOPASSWD:ALL' | sudo tee /etc/sudoers.d/create-user-external"
# Check we can connect with the new external user and make sudo
remote.exec --user external --pass ubuntu123 "sudo true"
}
nested_is_kvm_enabled() {
if [ -n "$NESTED_ENABLE_KVM" ]; then
[ "$NESTED_ENABLE_KVM" = true ]
fi
return 0
}
nested_is_tpm_enabled() {
if [ -n "$NESTED_ENABLE_TPM" ]; then
[ "$NESTED_ENABLE_TPM" = true ]
else
case "${SPREAD_SYSTEM:-}" in
ubuntu-1*)
return 1
;;
ubuntu-2*)
# TPM enabled by default on 20.04 and later
return 0
;;
*)
echo "unsupported system"
exit 1
;;
esac
fi
}
nested_is_secure_boot_enabled() {
if [ -n "$NESTED_ENABLE_SECURE_BOOT" ]; then
[ "$NESTED_ENABLE_SECURE_BOOT" = true ]
else
case "${SPREAD_SYSTEM:-}" in
ubuntu-1*)
return 1
;;
ubuntu-2*)
# secure boot enabled by default on 20.04 and later
return 0
;;
*)
echo "unsupported system"
exit 1
;;
esac
fi
}
nested_create_assertions_disk() {
mkdir -p "$NESTED_ASSETS_DIR"
local ASSERTIONS_DISK LOOP_DEV
ASSERTIONS_DISK="$NESTED_ASSETS_DIR/assertions.disk"
# make an image
dd if=/dev/null of="$ASSERTIONS_DISK" bs=1M seek=1
# format it as dos with a vfat partition
# TODO: can we do this more programmatically without printing into fdisk ?
printf 'o\nn\np\n1\n\n\nt\nc\nw\n' | fdisk "$ASSERTIONS_DISK"
# mount the disk image
kpartx -av "$ASSERTIONS_DISK"
# find the loopback device for the partition
LOOP_DEV=$(losetup --list | grep "$ASSERTIONS_DISK" | awk '{print $1}' | grep -Po "/dev/loop\K([0-9]*)")
# wait for the loop device to show up
retry -n 3 --wait 1 test -e "/dev/mapper/loop${LOOP_DEV}p1"
# make a vfat partition
mkfs.vfat -n SYSUSER "/dev/mapper/loop${LOOP_DEV}p1"
# mount the partition and copy the files
mkdir -p "$NESTED_ASSETS_DIR/sys-user-partition"
mount "/dev/mapper/loop${LOOP_DEV}p1" "$NESTED_ASSETS_DIR/sys-user-partition"
# use custom assertion if set
local AUTO_IMPORT_ASSERT
if [ -n "$NESTED_CUSTOM_AUTO_IMPORT_ASSERTION" ]; then
VERSION="$(nested_get_version)"
# shellcheck disable=SC2001
AUTO_IMPORT_ASSERT="$(echo "$NESTED_CUSTOM_AUTO_IMPORT_ASSERTION" | sed "s/{VERSION}/$VERSION/g")"
else
local per_model_auto
per_model_auto="$(nested_model_authority).auto-import.assert"
if [ -e "$TESTSLIB/assertions/${per_model_auto}" ]; then
AUTO_IMPORT_ASSERT="$TESTSLIB/assertions/${per_model_auto}"
else
AUTO_IMPORT_ASSERT="$TESTSLIB/assertions/auto-import.assert"
fi
fi
cp "$AUTO_IMPORT_ASSERT" "$NESTED_ASSETS_DIR/sys-user-partition/auto-import.assert"
# unmount the partition and the image disk
sudo umount "$NESTED_ASSETS_DIR/sys-user-partition"
sudo kpartx -d "$ASSERTIONS_DISK"
}
nested_qemu_name() {
if os.query is-arm; then
command -v qemu-system-aarch64
elif [ "$NESTED_ARCHITECTURE" = "i386" ]; then
command -v qemu-system-i386
else
command -v qemu-system-x86_64
fi
}
nested_get_snap_rev_for_channel() {
local SNAP=$1
local CHANNEL=$2
curl -s \
-H "Snap-Device-Architecture: $NESTED_ARCHITECTURE" \
-H "Snap-Device-Series: 16" \
-X POST \
-H "Content-Type: application/json" \
--data "{\"context\": [], \"actions\": [{\"action\": \"install\", \"name\": \"$SNAP\", \"channel\": \"$CHANNEL\", \"instance-key\": \"1\"}]}" \
https://api.snapcraft.io/v2/snaps/refresh | \
jq '.results[0].snap.revision'
}
nested_is_nested_system() {
if nested_is_core_system || nested_is_classic_system ; then
return 0
else
return 1
fi
}
nested_is_core_system() {
if [ -z "${NESTED_TYPE:-}" ]; then
echo "Variable NESTED_TYPE not defined."
return 1
fi
test "$NESTED_TYPE" = "core"
}
nested_is_classic_system() {
if [ -z "${NESTED_TYPE:-}" ]; then
echo "Variable NESTED_TYPE not defined."
return 1
fi
test "$NESTED_TYPE" = "classic"
}
nested_is_core_ge() {
local VERSION=$1
os.query is-ubuntu-ge "${VERSION}.04"
}
nested_is_core_gt() {
local VERSION=$1
os.query is-ubuntu-gt "${VERSION}.04"
}
nested_is_core_le() {
local VERSION=$1
os.query is-ubuntu-le "${VERSION}.04"
}
nested_is_core_lt() {
local VERSION=$1
os.query is-ubuntu-lt "${VERSION}.04"
}
nested_is_core_24_system() {
os.query is-noble
}
nested_is_core_22_system() {
os.query is-jammy
}
nested_is_core_20_system() {
os.query is-focal
}
nested_is_core_18_system() {
os.query is-bionic
}
nested_is_core_16_system() {
os.query is-xenial
}
nested_refresh_to_new_core() {
local NEW_CHANNEL=$1
local CHANGE_ID
if [ "$NEW_CHANNEL" = "" ]; then
echo "Channel to refresh is not defined."
exit 1
else
echo "Refreshing the core/snapd snap"
if nested_is_classic_nested_system; then
remote.exec "sudo snap refresh core --${NEW_CHANNEL}"
remote.exec "snap info core" | grep -E "^tracking: +latest/${NEW_CHANNEL}"
fi
if nested_is_core_ge 18; then
remote.exec "sudo snap refresh snapd --${NEW_CHANNEL}"
remote.exec "snap info snapd" | grep -E "^tracking: +latest/${NEW_CHANNEL}"
else
CHANGE_ID=$(remote.exec "sudo snap refresh core --${NEW_CHANNEL} --no-wait")
nested_wait_for_no_ssh 200 1
nested_wait_for_ssh 300 1
# wait for the refresh to be done before checking, if we check too
# quickly then operations on the core snap like reverting, etc. may
# fail because it will have refresh-snap change in progress
remote.exec "snap watch $CHANGE_ID"
remote.exec "snap info core" | grep -E "^tracking: +latest/${NEW_CHANNEL}"
fi
fi
}
nested_get_snakeoil_key() {
local KEYNAME="PkKek-1-snakeoil"
local VERSION
VERSION="$(nested_get_version)"
wget -q https://raw.githubusercontent.com/snapcore/pc-amd64-gadget/"$VERSION"/snakeoil/"$KEYNAME".key
wget -q https://raw.githubusercontent.com/snapcore/pc-amd64-gadget/"$VERSION"/snakeoil/"$KEYNAME".pem
echo "$KEYNAME"
}
nested_secboot_remove_signature() {
local FILE="$1"
while sbverify --list "$FILE" | grep "^signature [0-9]*$"; do
sbattach --remove "$FILE"
done
}
nested_secboot_sign_file() {
local FILE="$1"
local KEY="$2"
local CERT="$3"
nested_secboot_remove_signature "$FILE"
sbsign --key "$KEY" --cert "$CERT" --output "$FILE" "$FILE"
}
nested_secboot_sign_gadget() {
local GADGET_DIR="$1"
local KEY="$2"
local CERT="$3"
if [ -f "$GADGET_DIR/fb.efi" ]; then
nested_secboot_sign_file "$GADGET_DIR/fb.efi" "$KEY" "$CERT"
fi
nested_secboot_sign_file "$GADGET_DIR/shim.efi.signed" "$KEY" "$CERT"
}
nested_secboot_sign_kernel() {
local KERNEL_DIR="$1"
local KEY="$2"
local CERT="$3"
nested_secboot_sign_file "$KERNEL_DIR/kernel.efi" "$KEY" "$CERT"
}
nested_prepare_env() {
mkdir -p "$NESTED_IMAGES_DIR"
mkdir -p "$NESTED_RUNTIME_DIR"
mkdir -p "$NESTED_ASSETS_DIR"
mkdir -p "$NESTED_LOGS_DIR"
mkdir -p "$(nested_get_extra_snaps_path)"
}
nested_cleanup_env() {
rm -rf "$NESTED_RUNTIME_DIR"
rm -rf "$NESTED_ASSETS_DIR"
rm -rf "$NESTED_LOGS_DIR"
rm -rf "$NESTED_IMAGES_DIR"/*.img
rm -rf "$(nested_get_extra_snaps_path)"
}
nested_get_image_name() {
local TYPE="$1"
local SOURCE="${NESTED_CORE_CHANNEL}"
local NAME="${NESTED_IMAGE_ID:-generic}"
local VERSION
VERSION="$(nested_get_version)"
# Use task name to build the image in case the NESTED_IMAGE_ID is unset
# This scenario is valid on manual tests when it is required to set the NESTED_IMAGE_ID
if [ "$NAME" = "unset" ]; then
NAME="$(basename "$SPREAD_TASK")"
if [ -n "$SPREAD_VARIANT" ]; then
NAME="${NAME}_${SPREAD_VARIANT}"
fi
fi
if [ "$NESTED_BUILD_SNAPD_FROM_CURRENT" = "true" ]; then
SOURCE="custom"
fi
if [ "$(nested_get_extra_snaps | wc -l)" != "0" ]; then
SOURCE="custom"
fi
echo "ubuntu-${TYPE}-${VERSION}-${SOURCE}-${NAME}.img"
}
nested_is_generic_image() {
test -z "${NESTED_IMAGE_ID:-}"
}
nested_get_extra_snaps_path() {
echo "/tmp/extra-snaps"
}
nested_get_assets_path() {
echo "$NESTED_ASSETS_DIR"
}
nested_get_images_path() {
echo "$NESTED_IMAGES_DIR"
}
nested_get_extra_snaps() {
local EXTRA_SNAPS=""
local EXTRA_SNAPS_PATH
EXTRA_SNAPS_PATH="$(nested_get_extra_snaps_path)"
if [ -d "$EXTRA_SNAPS_PATH" ]; then
while IFS= read -r mysnap; do
echo "$mysnap"
done < <(find "$EXTRA_SNAPS_PATH" -name '*.snap')
fi
}
nested_download_image() {
local IMAGE_URL=$1
local IMAGE_NAME=$2
curl -C - -L -o "${NESTED_IMAGES_DIR}/${IMAGE_NAME}" "$IMAGE_URL"
if [[ "$IMAGE_URL" == *.img.xz ]]; then
mv "${NESTED_IMAGES_DIR}/${IMAGE_NAME}" "${NESTED_IMAGES_DIR}/${IMAGE_NAME}.xz"
unxz "${NESTED_IMAGES_DIR}/${IMAGE_NAME}.xz"
elif [[ "$IMAGE_URL" == *.img ]]; then
echo "Image doesn't need to be decompressed"
else
echo "Image extension not supported for image $IMAGE_URL, exiting..."
exit 1
fi
}
nested_get_version() {
if nested_is_core_16_system; then
echo "16"
elif nested_is_core_18_system; then
echo "18"
elif nested_is_core_20_system; then
echo "20"
elif nested_is_core_22_system; then
echo "22"
elif nested_is_core_24_system; then
echo "24"
fi
}
nested_get_model() {
# use custom model if defined
if [ -n "$NESTED_CUSTOM_MODEL" ]; then
VERSION="$(nested_get_version)"
# shellcheck disable=SC2001
echo "$NESTED_CUSTOM_MODEL" | sed "s/{VERSION}/$VERSION/g"
return
fi
case "$SPREAD_SYSTEM" in
ubuntu-16.04-64)
echo "$TESTSLIB/assertions/nested-amd64.model"
;;
ubuntu-18.04-64)
echo "$TESTSLIB/assertions/nested-18-amd64.model"
;;
ubuntu-20.04-64)
echo "$TESTSLIB/assertions/nested-20-amd64.model"
;;
ubuntu-22.04-64)
echo "$TESTSLIB/assertions/nested-22-amd64.model"
;;
ubuntu-22.04-arm-64)
echo "$TESTSLIB/assertions/nested-22-arm64.model"
;;
ubuntu-24.04-64)
echo "$TESTSLIB/assertions/nested-24-amd64.model"
;;
*)
echo "unsupported system"
exit 1
;;
esac
}
nested_model_authority() {
local model
model="$(nested_get_model)"
grep "authority-id:" "$model"|cut -d ' ' -f2
}
nested_ensure_ubuntu_save() {
local GADGET_DIR="$1"
shift
"$TESTSLIB"/ensure_ubuntu_save.py "$@" "$GADGET_DIR"/meta/gadget.yaml > /tmp/gadget-with-save.yaml
if [ "$(cat /tmp/gadget-with-save.yaml)" != "" ]; then
mv /tmp/gadget-with-save.yaml "$GADGET_DIR"/meta/gadget.yaml
else
rm -f /tmp/gadget-with-save.yaml
fi
}
nested_prepare_snapd() {
if [ "$NESTED_BUILD_SNAPD_FROM_CURRENT" = "true" ]; then
echo "Repacking snapd snap"
local snap_name output_name snap_id
if nested_is_core_16_system; then
if [ ! -f "$NESTED_ASSETS_DIR/core-from-snapd-deb.snap" ]; then
"$TESTSTOOLS"/snaps-state repack_snapd_deb_into_snap core "$NESTED_ASSETS_DIR"
cp "$NESTED_ASSETS_DIR/core-from-snapd-deb.snap" "$(nested_get_extra_snaps_path)/core-from-snapd-deb.snap"
fi
# sign the snapd snap with fakestore if requested
if [ "$NESTED_SIGN_SNAPS_FAKESTORE" = "true" ]; then
"$TESTSTOOLS"/store-state make-snap-installable --noack "$NESTED_FAKESTORE_BLOB_DIR" "$(nested_get_extra_snaps_path)/core-from-snapd-deb.snap" "99T7MUlRhtI3U0QFgl5mXXESAiSwt776"
fi
else
for f in "${NESTED_ASSETS_DIR}"/snapd_*.snap; do
snap_name="$(basename "${f}")"
break
done
if [ ! -f "${NESTED_ASSETS_DIR}/${snap_name}" ]; then
# shellcheck source=tests/lib/prepare.sh
. "$TESTSLIB"/prepare.sh
build_snapd_snap "$NESTED_ASSETS_DIR"
for f in "${NESTED_ASSETS_DIR}"/snapd_*.snap; do
snap_name="$(basename "${f}")"
break
done
cp "${NESTED_ASSETS_DIR}/${snap_name}" "$(nested_get_extra_snaps_path)/"
fi
# sign the snapd snap with fakestore if requested
if [ "$NESTED_SIGN_SNAPS_FAKESTORE" = "true" ]; then
"$TESTSTOOLS"/store-state make-snap-installable --noack "$NESTED_FAKESTORE_BLOB_DIR" "$(nested_get_extra_snaps_path)/${snap_name}" "PMrrV4ml8uWuEUDBT8dSGnKUYbevVhc4"
fi
fi
fi
}
nested_prepare_kernel() {
# allow repacking the kernel
if [ "$NESTED_REPACK_KERNEL_SNAP" = "true" ]; then
echo "Repacking kernel snap"
local kernel_snap output_name snap_id version
output_name="pc-kernel.snap"
snap_id="pYVQrBcKmBa0mZ4CCN7ExT6jH8rY1hza"
version="$(nested_get_version)"
if [ ! -f "$NESTED_ASSETS_DIR/$output_name" ]; then
if nested_is_core_le 18; then
kernel_snap=pc-kernel-new.snap
repack_kernel_snap "$kernel_snap"
elif nested_is_core_ge 20; then
snap download --basename=pc-kernel --channel="$version/${NESTED_KERNEL_CHANNEL}" pc-kernel
# set the unix bump time if the NESTED_* var is set,
# otherwise leave it empty
local epochBumpTime
epochBumpTime=${NESTED_CORE20_INITRAMFS_EPOCH_TIMESTAMP:-}
if [ -n "$epochBumpTime" ]; then
epochBumpTime="--epoch-bump-time=$epochBumpTime"
fi
if nested_is_core_24_system; then
uc24_build_initramfs_kernel_snap "pc-kernel.snap" "$NESTED_ASSETS_DIR" "$epochBumpTime"
else
uc20_build_initramfs_kernel_snap "pc-kernel.snap" "$NESTED_ASSETS_DIR" "$epochBumpTime"
fi
rm -f "pc-kernel.snap" "pc-kernel.assert"
# Prepare the pc kernel snap
kernel_snap=$(ls "$NESTED_ASSETS_DIR"/pc-kernel_*.snap)
chmod 0600 "$kernel_snap"
fi
mv "$kernel_snap" "$NESTED_ASSETS_DIR/$output_name"
fi
cp "$NESTED_ASSETS_DIR/$output_name" "$(nested_get_extra_snaps_path)/$output_name"
# sign the pc-kernel snap with fakestore if requested
if [ "$NESTED_SIGN_SNAPS_FAKESTORE" = "true" ]; then
"$TESTSTOOLS"/store-state make-snap-installable --noack "$NESTED_FAKESTORE_BLOB_DIR" "$(nested_get_extra_snaps_path)/$output_name" "$snap_id"
fi
fi
}
nested_prepare_gadget() {
if [ "$NESTED_REPACK_GADGET_SNAP" = "true" ]; then
if nested_is_core_ge 20; then
# Prepare the pc gadget snap (unless provided by extra-snaps)
local snap_id version gadget_snap
version="$(nested_get_version)"
snap_id="UqFziVZDHLSyO3TqSWgNBoAdHbLI4dAH"
existing_snap=$(find "$(nested_get_extra_snaps_path)" -name 'pc_*.snap')
if [ -n "$existing_snap" ]; then
echo "Using generated pc gadget snap $existing_snap"
if [ "$NESTED_SIGN_SNAPS_FAKESTORE" = "true" ]; then
"$TESTSTOOLS"/store-state make-snap-installable --noack --extra-decl-json "$NESTED_FAKESTORE_SNAP_DECL_PC_GADGET" "$NESTED_FAKESTORE_BLOB_DIR" "$existing_snap" "$snap_id"
fi
return
fi
# XXX: deal with [ "$NESTED_ENABLE_SECURE_BOOT" != "true" ] && [ "$NESTED_ENABLE_TPM" != "true" ]
echo "Repacking pc snap"
# Get the snakeoil key and cert
local key_name snakeoil_key snakeoil_cert
key_name=$(nested_get_snakeoil_key)
snakeoil_key="$PWD/$key_name.key"
snakeoil_cert="$PWD/$key_name.pem"
snap download --basename=pc --channel="$version/${NESTED_GADGET_CHANNEL}" pc
unsquashfs -d pc-gadget pc.snap
nested_secboot_sign_gadget pc-gadget "$snakeoil_key" "$snakeoil_cert"
case "${NESTED_UBUNTU_SAVE:-}" in
add)
# ensure that ubuntu-save is present
nested_ensure_ubuntu_save pc-gadget --add
touch ubuntu-save-added
;;
remove)
# ensure that ubuntu-save is removed
nested_ensure_ubuntu_save pc-gadget --remove
touch ubuntu-save-removed
;;
esac
# also make logging persistent for easier debugging of
# test failures, otherwise we have no way to see what
# happened during a failed nested VM boot where we
# weren't able to login to a device
cat >> pc-gadget/meta/gadget.yaml << EOF
defaults:
system:
journal:
persistent: true
EOF
local GADGET_EXTRA_CMDLINE=""
if [ "$NESTED_SNAPD_DEBUG_TO_SERIAL" = "true" ]; then
# add snapd debug and log to serial console for extra
# visibility what happens when a machine fails to boot
GADGET_EXTRA_CMDLINE="console=ttyS0 snapd.debug=1 systemd.journald.forward_to_console=1"
fi
if [ -n "$NESTED_EXTRA_CMDLINE" ]; then
GADGET_EXTRA_CMDLINE="$GADGET_EXTRA_CMDLINE $NESTED_EXTRA_CMDLINE"
fi
if [ -n "$GADGET_EXTRA_CMDLINE" ]; then
echo "Configuring command line parameters in the gadget snap: \"console=ttyS0 $GADGET_EXTRA_CMDLINE\""
echo "$GADGET_EXTRA_CMDLINE" > pc-gadget/cmdline.extra
fi
# pack the gadget
snap pack pc-gadget/ "$NESTED_ASSETS_DIR"
gadget_snap=$(ls "$NESTED_ASSETS_DIR"/pc_*.snap)
cp "$gadget_snap" "$(nested_get_extra_snaps_path)/pc.snap"
rm -f "pc.snap" "pc.assert" "$snakeoil_key" "$snakeoil_cert"
fi
# sign the pc gadget snap with fakestore if requested
if [ "$NESTED_SIGN_SNAPS_FAKESTORE" = "true" ]; then
# XXX: this is a bit of a hack, but some nested tests
# need extra bits in their snap declaration, so inject
# that here, it could end up being empty in which case
# it is ignored
"$TESTSTOOLS"/store-state make-snap-installable --noack --extra-decl-json "$NESTED_FAKESTORE_SNAP_DECL_PC_GADGET" "$NESTED_FAKESTORE_BLOB_DIR" "$(nested_get_extra_snaps_path)/pc.snap" "$snap_id"
fi
fi
}
nested_prepare_base() {
if [ "$NESTED_REPACK_BASE_SNAP" = "true" ]; then
if nested_is_core_16_system; then
echo "No base snap to prepare in core 16"
return
elif nested_is_core_18_system; then
snap_name="core18"
snap_id="CSO04Jhav2yK0uz97cr0ipQRyqg0qQL6"
elif nested_is_core_20_system; then
snap_name="core20"
snap_id="DLqre5XGLbDqg9jPtiAhRRjDuPVa5X1q"
elif nested_is_core_22_system; then
snap_name="core22"
snap_id="amcUKQILKXHHTlmSa7NMdnXSx02dNeeT"
elif nested_is_core_24_system; then
snap_name="core24"
snap_id="dwTAh7MZZ01zyriOZErqd1JynQLiOGvM"
fi
output_name="${snap_name}.snap"
existing_snap=$(find "$(nested_get_extra_snaps_path)" -name "${snap_name}*.snap")
if [ -n "$existing_snap" ]; then
echo "Using generated base snap $existing_snap"
if [ "$NESTED_SIGN_SNAPS_FAKESTORE" = "true" ]; then
"$TESTSTOOLS"/store-state make-snap-installable --noack "$NESTED_FAKESTORE_BLOB_DIR" "$existing_snap" "$snap_id"
fi
return
fi
if [ ! -f "$NESTED_ASSETS_DIR/$output_name" ]; then
echo "Repacking $snap_name snap"
snap download --channel="$CORE_CHANNEL" --basename="$snap_name" "$snap_name"
repack_core_snap_with_tweaks "${snap_name}.snap" "new-${snap_name}.snap"
rm -f "$snap_name".snap "$snap_name".assert
mv "new-${snap_name}.snap" "$NESTED_ASSETS_DIR/$output_name"
fi
cp "$NESTED_ASSETS_DIR/$output_name" "$(nested_get_extra_snaps_path)/$output_name"
# sign the base snap with fakestore if requested
if [ "$NESTED_SIGN_SNAPS_FAKESTORE" = "true" ]; then
"$TESTSTOOLS"/store-state make-snap-installable --noack "$NESTED_FAKESTORE_BLOB_DIR" "$(nested_get_extra_snaps_path)/${snap_name}.snap" "$snap_id"
fi
fi
}
nested_prepare_essential_snaps() {
# shellcheck source=tests/lib/prepare.sh
. "$TESTSLIB"/prepare.sh
# shellcheck source=tests/lib/snaps.sh
. "$TESTSLIB"/snaps.sh
nested_prepare_snapd
nested_prepare_kernel
nested_prepare_gadget
nested_prepare_base
}
nested_configure_default_user() {
local IMAGE_NAME
IMAGE_NAME="$(nested_get_image_name core)"
# Configure the user for the vm
if [ "$NESTED_USE_CLOUD_INIT" = "true" ]; then
if nested_is_core_ge 20; then
nested_configure_cloud_init_on_core20_vm "$NESTED_IMAGES_DIR/$IMAGE_NAME"
else
nested_configure_cloud_init_on_core_vm "$NESTED_IMAGES_DIR/$IMAGE_NAME"
fi
else
nested_create_assertions_disk
fi
# Save a copy of the image
cp -v "$NESTED_IMAGES_DIR/$IMAGE_NAME" "$NESTED_IMAGES_DIR/$IMAGE_NAME.pristine"
}
nested_create_core_vm() {
# shellcheck source=tests/lib/prepare.sh
. "$TESTSLIB"/prepare.sh
# shellcheck source=tests/lib/snaps.sh
. "$TESTSLIB"/snaps.sh
local IMAGE_NAME
IMAGE_NAME="$(nested_get_image_name core)"
mkdir -p "$NESTED_IMAGES_DIR"
if [ -f "$NESTED_IMAGES_DIR/$IMAGE_NAME.pristine" ]; then
cp -v "$NESTED_IMAGES_DIR/$IMAGE_NAME.pristine" "$NESTED_IMAGES_DIR/$IMAGE_NAME"
if [ ! "$NESTED_USE_CLOUD_INIT" = "true" ]; then
nested_create_assertions_disk
fi
return
elif [ ! -f "$NESTED_IMAGES_DIR/$IMAGE_NAME" ]; then
if [ -n "$NESTED_CUSTOM_IMAGE_URL" ]; then
# download the ubuntu-core image from $CUSTOM_IMAGE_URL
nested_download_image "$NESTED_CUSTOM_IMAGE_URL" "$IMAGE_NAME"
else
# create the ubuntu-core image
local UBUNTU_IMAGE="$GOHOME"/bin/ubuntu-image
if os.query is-xenial || os.query is-arm; then
# ubuntu-image on 16.04 needs to be installed from a snap
UBUNTU_IMAGE=/snap/bin/ubuntu-image
fi
if [ "$NESTED_BUILD_SNAPD_FROM_CURRENT" = "true" ]; then
nested_prepare_snapd
nested_prepare_kernel
nested_prepare_gadget
nested_prepare_base
fi
# Invoke ubuntu image
local NESTED_MODEL
NESTED_MODEL="$(nested_get_model)"
local EXTRA_SNAPS=""
for mysnap in $(nested_get_extra_snaps); do
EXTRA_SNAPS="$EXTRA_SNAPS --snap $mysnap"
done
# only set SNAPPY_FORCE_SAS_URL because we don't need it defined
# anywhere else but here, where snap prepare-image as called by
# ubuntu-image will look for assertions for the snaps we provide
# to it
SNAPPY_FORCE_SAS_URL="$NESTED_UBUNTU_IMAGE_SNAPPY_FORCE_SAS_URL"
export SNAPPY_FORCE_SAS_URL
UBUNTU_IMAGE_SNAP_CMD=/usr/bin/snap
export UBUNTU_IMAGE_SNAP_CMD
if [ -n "$NESTED_CORE_CHANNEL" ]; then
UBUNTU_IMAGE_CHANNEL_ARG="--channel $NESTED_CORE_CHANNEL"
else
UBUNTU_IMAGE_CHANNEL_ARG=""
fi
declare -a UBUNTU_IMAGE_PRESEED_ARGS
if [ -n "$NESTED_UBUNTU_IMAGE_PRESEED_KEY" ]; then
# shellcheck disable=SC2191
UBUNTU_IMAGE_PRESEED_ARGS+=(--preseed --preseed-sign-key=\""$NESTED_UBUNTU_IMAGE_PRESEED_KEY"\")
fi
# ubuntu-image creates sparse image files
# shellcheck disable=SC2086
SNAPD_DEBUG=1 "$UBUNTU_IMAGE" snap --image-size 10G --validation=enforce \
"$NESTED_MODEL" \
$UBUNTU_IMAGE_CHANNEL_ARG \
"${UBUNTU_IMAGE_PRESEED_ARGS[@]:-}" \
--output-dir "$NESTED_IMAGES_DIR" \
--sector-size "${NESTED_DISK_LOGICAL_BLOCK_SIZE}" \
$EXTRA_SNAPS |& tee "$NESTED_LOGS_DIR/ubuntu-image.log"
# ubuntu-image dropped the --output parameter, so we have to rename
# the image ourselves, the images are named after volumes listed in
# gadget.yaml
find "$NESTED_IMAGES_DIR/" -maxdepth 1 -name '*.img' | while read -r imgname; do
if [ -e "$NESTED_IMAGES_DIR/$IMAGE_NAME" ]; then
echo "Image $IMAGE_NAME file already present"
exit 1
fi
mv "$imgname" "$NESTED_IMAGES_DIR/$IMAGE_NAME"
done
unset SNAPPY_FORCE_SAS_URL
unset UBUNTU_IMAGE_SNAP_CMD
fi
fi
nested_configure_default_user
}
nested_configure_cloud_init_on_core_vm() {
local IMAGE=$1
nested_create_cloud_init_data "$NESTED_ASSETS_DIR/user-data" "$NESTED_ASSETS_DIR/meta-data"
local devloop writableDev tmp
# mount the image and find the loop device /dev/loop that is created for it
kpartx -avs "$IMAGE"
devloop=$(losetup --list --noheadings | grep "$IMAGE" | awk '{print $1}')
dev=$(basename "$devloop")
# we add cloud-init data to the 3rd partition, which is writable
writableDev="/dev/mapper/${dev}p3"
# wait for the loop device to show up
retry -n 3 --wait 1 test -e "$writableDev"
tmp=$(mktemp -d)
mount "$writableDev" "$tmp"
# use nocloud-net for the dir to copy data into
mkdir -p "$tmp/system-data/var/lib/cloud/seed/nocloud-net/"
cp "$NESTED_ASSETS_DIR/user-data" "$tmp/system-data/var/lib/cloud/seed/nocloud-net/"
cp "$NESTED_ASSETS_DIR/meta-data" "$tmp/system-data/var/lib/cloud/seed/nocloud-net/"
sync
umount "$tmp"
kpartx -d "$IMAGE"
}
nested_create_cloud_init_data() {
local USER_DATA=$1
local META_DATA=$2
cat <<EOF > "$USER_DATA"
#cloud-config
ssh_pwauth: True
users:
- name: user1
sudo: ALL=(ALL) NOPASSWD:ALL
shell: /bin/bash
chpasswd:
list: |
user1:ubuntu
expire: False
EOF
cat <<EOF > "$META_DATA"
instance_id: cloud-images
EOF
}
# TODO: see if the uc20 config works for classic here too, that would be faster
# as the chpasswd module from cloud-init runs rather late in the boot
nested_create_cloud_init_config() {
local CONFIG_PATH=$1
cat <<EOF > "$CONFIG_PATH"
#cloud-config
ssh_pwauth: True
users:
- name: user1
sudo: ALL=(ALL) NOPASSWD:ALL
shell: /bin/bash
chpasswd:
list: |
user1:ubuntu
expire: False
datasource_list: [ NoCloud, None ]
datasource:
NoCloud:
userdata_raw: |
#!/bin/bash
logger -t nested test running || true
EOF
}
nested_create_cloud_init_uc20_config() {
local CONFIG_PATH=$1
cat << 'EOF' > "$CONFIG_PATH"
#cloud-config
datasource_list: [ None ]
users:
- name: user1
sudo: "ALL=(ALL) NOPASSWD:ALL"
lock_passwd: false
plain_text_passwd: "ubuntu"
EOF
}
nested_add_file_to_image() {
local IMAGE=$1
local FILE=$2
local devloop ubuntuSeedDev tmp
# Bind the image the a free loop device
devloop="$(retry -n 3 --wait 1 losetup -f --show -P --sector-size "${NESTED_DISK_LOGICAL_BLOCK_SIZE}" "${IMAGE}")"
# we add cloud-init data to the 2nd partition, which is ubuntu-seed
ubuntuSeedDev="${devloop}p2"
if os.query is-arm; then
# In arm the BIOS partition does not exist, so ubuntu-seed is the p1
ubuntuSeedDev="${devloop}p1"
fi
# Wait for the partition to show up
retry -n 2 --wait 1 test -b "${ubuntuSeedDev}" || true
# losetup does not set the right block size on LOOP_CONFIGURE
# but with LOOP_SET_BLOCK_SIZE later. So the block size might have
# been wrong during the part scan. In this case we need to rescan
# manually.
if ! [ -b "${ubuntuSeedDev}" ]; then
partx -u "${devloop}"
# Wait for the partition to show up
retry -n 2 --wait 1 test -b "${ubuntuSeedDev}"
fi
tmp=$(mktemp -d)
retry -n 5 --wait 2 mount "$ubuntuSeedDev" "$tmp"
mkdir -p "$tmp/data/etc/cloud/cloud.cfg.d/"
cp -f "$FILE" "$tmp/data/etc/cloud/cloud.cfg.d/"
sync
umount "$tmp"
losetup -d "${devloop}"
}
nested_configure_cloud_init_on_core20_vm() {
local IMAGE=$1
nested_create_cloud_init_uc20_config "$NESTED_ASSETS_DIR/data.cfg"
nested_add_file_to_image "$IMAGE" "$NESTED_ASSETS_DIR/data.cfg"
}
nested_save_serial_log() {
if [ -f "${NESTED_LOGS_DIR}/serial.log" ]; then
for i in $(seq 1 9); do
if [ ! -f "${NESTED_LOGS_DIR}/serial.log.${i}" ]; then
cp "${NESTED_LOGS_DIR}/serial.log" "${NESTED_LOGS_DIR}/serial.log.${i}"
break
fi
done
# make sure we start with clean log file
echo > "${NESTED_LOGS_DIR}/serial.log"
fi
}
nested_print_serial_log() {
if [ -f "${NESTED_LOGS_DIR}/serial.log.1" ]; then
# here we disable SC2045 because previously it is checked there is at least
# 1 file which matches. In this case ls command is needed because it is important
# to get the list in reverse order.
# shellcheck disable=SC2045
for logfile in $(ls "${NESTED_LOGS_DIR}"/serial.log.*); do
cat "$logfile"
done
fi
if [ -f "${NESTED_LOGS_DIR}/serial.log" ]; then
cat "${NESTED_LOGS_DIR}/serial.log"
fi
}
nested_force_stop_vm() {
systemctl stop "$NESTED_VM"
}
nested_force_start_vm() {
# if the $NESTED_VM is using a swtpm, we need to wait until the file exists
# because the file disappears temporarily after qemu exits
if systemctl show "$NESTED_VM" -p ExecStart | grep -q test-snapd-swtpm; then
retry -n 10 --wait 1 test -S /var/snap/test-snapd-swtpm/current/swtpm-sock
fi
systemctl start "$NESTED_VM"
}
nested_start_core_vm_unit() {
local QEMU CURRENT_IMAGE
CURRENT_IMAGE=$1
QEMU=$(nested_qemu_name)
# Now qemu parameters are defined
# use only 2G of RAM for qemu-nested
# the caller can override PARAM_MEM
local PARAM_MEM PARAM_SMP
if [ "$SPREAD_BACKEND" = "google-nested" ] || [ "$SPREAD_BACKEND" = "google-nested-arm" ]; then
PARAM_MEM="-m ${NESTED_MEM:-4096}"
PARAM_SMP="-smp ${NESTED_CPUS:-2}"
elif [ "$SPREAD_BACKEND" = "google-nested-dev" ]; then
PARAM_MEM="-m ${NESTED_MEM:-8192}"
PARAM_SMP="-smp ${NESTED_CPUS:-4}"
elif [ "$SPREAD_BACKEND" = "qemu-nested" ]; then
PARAM_MEM="-m ${NESTED_MEM:-2048}"
PARAM_SMP="-smp ${NESTED_CPUS:-1}"
else
echo "unknown spread backend $SPREAD_BACKEND"
exit 1
fi
PARAM_PHYS_BLOCK_SIZE="physical_block_size=${NESTED_DISK_PHYSICAL_BLOCK_SIZE}"
PARAM_LOGI_BLOCK_SIZE="logical_block_size=${NESTED_DISK_LOGICAL_BLOCK_SIZE}"
local PARAM_DISPLAY PARAM_NETWORK PARAM_MONITOR PARAM_USB PARAM_CD PARAM_RANDOM PARAM_CPU PARAM_TRACE PARAM_LOG PARAM_SERIAL PARAM_RTC
PARAM_DISPLAY="-nographic"
PARAM_NETWORK="-net nic,model=virtio -net user,hostfwd=tcp::$NESTED_SSH_PORT-:22,hostfwd=tcp::8023-:8023,hostfwd=tcp::9022-:9022"
PARAM_MONITOR="-monitor tcp:127.0.0.1:$NESTED_MON_PORT,server=on,wait=off"
PARAM_USB="-usb"
PARAM_CD="${NESTED_PARAM_CD:-}"
PARAM_RANDOM="-object rng-random,id=rng0,filename=/dev/urandom -device virtio-rng-pci,rng=rng0"
PARAM_CPU=""
PARAM_TRACE="-d cpu_reset"
PARAM_LOG="-D $NESTED_LOGS_DIR/qemu.log"
PARAM_RTC="${NESTED_PARAM_RTC:-}"
PARAM_EXTRA="${NESTED_PARAM_EXTRA:-}"
# Open port 7777 on the host so that failures in the nested VM (e.g. to
# create users) can be debugged interactively via
# "telnet localhost 7777". Also keeps the logs
#
# XXX: should serial just be logged to stdout so that we just need
# to "journalctl -u $NESTED_VM" to see what is going on ?
if "$QEMU" -version | grep '2\.5'; then
# XXX: remove once we no longer support xenial hosts
PARAM_SERIAL="-serial file:${NESTED_LOGS_DIR}/serial.log"
else
PARAM_SERIAL="-chardev socket,telnet=on,host=localhost,server=on,port=7777,wait=off,id=char0,logfile=${NESTED_LOGS_DIR}/serial.log,logappend=on -serial chardev:char0"
fi
# save logs from previous runs
nested_save_serial_log
# Set kvm attribute
local ATTR_KVM
ATTR_KVM=""
if nested_is_kvm_enabled; then
ATTR_KVM=",accel=kvm"
# CPU can be defined just when kvm is enabled
PARAM_CPU="-cpu host"
fi
local PARAM_MACHINE
if [[ "$SPREAD_BACKEND" = google-nested* ]]; then
if os.query is-arm; then
PARAM_MACHINE="-machine virt${ATTR_KVM}"
PARAM_CPU="-cpu host"
else
PARAM_MACHINE="-machine ubuntu${ATTR_KVM}"
fi
elif [ "$SPREAD_BACKEND" = "qemu-nested" ]; then
# check if we have nested kvm
if [ "$(cat /sys/module/kvm_*/parameters/nested)" = "1" ]; then
PARAM_MACHINE="-machine ubuntu${ATTR_KVM}"
else
# and if not reset kvm related parameters
PARAM_MACHINE=""
PARAM_CPU=""
ATTR_KVM=""
fi
else
echo "unknown spread backend $SPREAD_BACKEND"
exit 1
fi
local PARAM_ASSERTIONS PARAM_BIOS PARAM_TPM PARAM_IMAGE
PARAM_ASSERTIONS=""
PARAM_BIOS=""
PARAM_TPM=""
PARAM_REEXEC_ON_FAILURE=""
if [ "$NESTED_USE_CLOUD_INIT" != "true" ]; then
# TODO: fix using the old way of an ext4 formatted drive w/o partitions
# as this used to work but has since regressed
# this simulates a usb drive attached to the device, the removable=true
# is necessary otherwise snapd will not import it, as snapd only
# considers removable devices for cold-plug first-boot runs
# the nec-usb-xhci device is necessary to create the bus we attach the
# storage to
PARAM_ASSERTIONS="-drive if=none,id=stick,format=raw,file=$NESTED_ASSETS_DIR/assertions.disk,cache=none,format=raw -device nec-usb-xhci,id=xhci -device usb-storage,bus=xhci.0,removable=true,drive=stick"
fi
if nested_is_core_ge 20; then
# use a bundle EFI bios by default
if os.query is-arm; then
PARAM_BIOS="-bios /usr/share/AAVMF/AAVMF_CODE.fd"
else
PARAM_BIOS="-bios /usr/share/ovmf/OVMF.fd"
fi
local OVMF_CODE OVMF_VARS
OVMF_CODE=""
OVMF_VARS=""
if nested_is_core_ge 22; then
wget -q https://storage.googleapis.com/snapd-spread-tests/dependencies/OVMF_CODE.secboot.fd
mv OVMF_CODE.secboot.fd /usr/share/OVMF/OVMF_CODE.secboot.fd
wget -q https://storage.googleapis.com/snapd-spread-tests/dependencies/OVMF_VARS.snakeoil.fd
mv OVMF_VARS.snakeoil.fd /usr/share/OVMF/OVMF_VARS.snakeoil.fd
wget -q https://storage.googleapis.com/snapd-spread-tests/dependencies/OVMF_VARS.ms.fd
mv OVMF_VARS.ms.fd /usr/share/OVMF/OVMF_VARS.ms.fd
fi
# In this case the kernel.efi is unsigned and signed with snaleoil certs
if [ "$NESTED_FORCE_MS_KEYS" != "true" ] && [ "$NESTED_BUILD_SNAPD_FROM_CURRENT" = "true" ]; then
OVMF_VARS="snakeoil"
else
OVMF_VARS="ms"
fi
if nested_is_secure_boot_enabled; then
OVMF_CODE="secboot"
if os.query is-arm; then
cp -f "/usr/share/AAVMF/AAVMF_VARS.fd" "$NESTED_ASSETS_DIR/AAVMF_VARS.fd"
PARAM_BIOS="-drive file=/usr/share/AAVMF/AAVMF_CODE.fd,if=pflash,format=raw,unit=0,readonly=on -drive file=$NESTED_ASSETS_DIR/AAVMF_VARS.fd,if=pflash,format=raw"
else
cp -f "/usr/share/OVMF/OVMF_VARS.${OVMF_VARS}.fd" "$NESTED_ASSETS_DIR/OVMF_VARS.${OVMF_VARS}.fd"
PARAM_BIOS="-drive file=/usr/share/OVMF/OVMF_CODE.${OVMF_CODE}.fd,if=pflash,format=raw,unit=0,readonly=on -drive file=$NESTED_ASSETS_DIR/OVMF_VARS.${OVMF_VARS}.fd,if=pflash,format=raw"
PARAM_MACHINE="-machine q35${ATTR_KVM} -global ICH9-LPC.disable_s3=1"
fi
fi
if nested_is_tpm_enabled; then
if snap list test-snapd-swtpm >/dev/null; then
if [ -z "$NESTED_TPM_NO_RESTART" ]; then
# reset the tpm state
snap stop test-snapd-swtpm > /dev/null
rm /var/snap/test-snapd-swtpm/current/tpm2-00.permall || true
snap start test-snapd-swtpm > /dev/null
fi
else
snap install test-snapd-swtpm --edge
fi
# wait for the tpm sock file to exist
retry -n 10 --wait 1 test -S /var/snap/test-snapd-swtpm/current/swtpm-sock
PARAM_TPM="-chardev socket,id=chrtpm,path=/var/snap/test-snapd-swtpm/current/swtpm-sock -tpmdev emulator,id=tpm0,chardev=chrtpm"
if os.query is-arm; then
PARAM_TPM="$PARAM_TPM -device tpm-tis-device,tpmdev=tpm0"
else
PARAM_TPM="$PARAM_TPM -device tpm-tis,tpmdev=tpm0"
fi
fi
PARAM_IMAGE="-drive file=$CURRENT_IMAGE,cache=none,format=raw,id=disk1,if=none -device virtio-blk-pci,drive=disk1,bootindex=1"
else
PARAM_IMAGE="-drive file=$CURRENT_IMAGE,cache=none,format=raw,id=disk1,if=none -device ide-hd,drive=disk1"
fi
PARAM_IMAGE="$PARAM_IMAGE,${PARAM_PHYS_BLOCK_SIZE},${PARAM_LOGI_BLOCK_SIZE}"
if nested_is_core_20_system; then
# This is to deal with the following qemu error which occurs using q35 machines in focal
# Error -> Code=qemu-system-x86_64: /build/qemu-rbeYHu/qemu-4.2/include/hw/core/cpu.h:633: cpu_asidx_from_attrs: Assertion `ret < cpu->num_ases && ret >= 0' failed
# It is reproducible on an Intel machine without unrestricted mode support, the failure is most likely due to the guest entering an invalid state for Intel VT
# The workaround is to restart the vm and check that qemu doesn't go into this bad state again
PARAM_REEXEC_ON_FAILURE="[Service]\nRestart=on-failure\nRestartSec=5s"
fi
# ensure we have a log dir
mkdir -p "$NESTED_LOGS_DIR"
# make sure we start with clean log file
echo > "${NESTED_LOGS_DIR}/serial.log"
# Systemd unit is created, it is important to respect the qemu parameters order
tests.systemd create-and-start-unit "$NESTED_VM" "${QEMU} \
${PARAM_SMP} \
${PARAM_CPU} \
${PARAM_MEM} \
${PARAM_TRACE} \
${PARAM_LOG} \
${PARAM_RTC} \
${PARAM_MACHINE} \
${PARAM_DISPLAY} \
${PARAM_NETWORK} \
${PARAM_BIOS} \
${PARAM_TPM} \
${PARAM_RANDOM} \
${PARAM_IMAGE} \
${PARAM_ASSERTIONS} \
${PARAM_SERIAL} \
${PARAM_MONITOR} \
${PARAM_USB} \
${PARAM_CD} \
${PARAM_EXTRA} " "${PARAM_REEXEC_ON_FAILURE}"
local EXPECT_SHUTDOWN
EXPECT_SHUTDOWN=${NESTED_EXPECT_SHUTDOWN:-}
if [ "$EXPECT_SHUTDOWN" != "1" ]; then
# Wait until the vm is ready to receive connections
if ! nested_wait_vm_ready 120; then
echo "failed to wait for the vm becomes ready to receive connections"
return 1
fi
# Wait for the snap command to be available
nested_wait_for_snap_command 120 1
# Wait for snap seeding to be done
# retry this wait command up to 3 times since we sometimes see races
# where the snap command appears, then immediately disappears and then
# re-appears immediately after and so the next command fails
attempts=0
until remote.exec "sudo snap wait system seed.loaded"; do
attempts=$(( attempts + 1))
if [ "$attempts" = 3 ]; then
echo "failed to wait for snap wait command to return successfully"
return 1
fi
sleep 1
done
# Copy tools to be used on tests
nested_prepare_tools
# Wait for cloud init to be done if the system is using cloud-init
if [ "$NESTED_USE_CLOUD_INIT" = true ]; then
if ! remote.exec "retry --wait 1 -n 5 sh -c 'cloud-init status --wait'"; then
# Error 2 means 'recoverable error', ignore that case
ret=0
remote.exec "cloud-init status" || ret=$?
if [ "$ret" -ne 0 ] && [ "$ret" -ne 2 ]; then
echo "cloud-init finished with error $ret"
exit 1
fi
fi
fi
fi
}
nested_get_current_image_name() {
echo "ubuntu-core-current.img"
}
nested_start_core_vm() {
local CURRENT_IMAGE CURRENT_NAME
CURRENT_NAME="$(nested_get_current_image_name)"
CURRENT_IMAGE="$NESTED_IMAGES_DIR/$CURRENT_NAME"
# In case the current image already exists, it needs to be reused and in that
# case is neither required to copy the base image nor prepare the ssh
if [ ! -f "$CURRENT_IMAGE" ]; then
# As core18 systems use to fail to start the assertion disk when using the
# snapshot feature, we copy the original image and use that copy to start
# the VM.
# Some tests however need to force stop and restart the VM with different
# options, so if that env var is set, we will reuse the existing file if it
# exists
local IMAGE_NAME
IMAGE_NAME="$(nested_get_image_name core)"
if ! [ -f "$NESTED_IMAGES_DIR/$IMAGE_NAME" ]; then
echo "No image found to be started"
exit 1
fi
# images are created as sparse files, simple cp should preserve that
# property
cp -v "$NESTED_IMAGES_DIR/$IMAGE_NAME" "$CURRENT_IMAGE"
# Start the nested core vm
nested_start_core_vm_unit "$CURRENT_IMAGE"
if [ ! -f "$NESTED_IMAGES_DIR/$IMAGE_NAME.configured" ]; then
# configure ssh for first time
nested_prepare_ssh
sync
# keep a copy of the current image if it is a generic image
if nested_is_generic_image && [ "$NESTED_CONFIGURE_IMAGES" = "true" ]; then
# Stop the current image and compress it
nested_shutdown
# Save the image with the name of the original image
cp -v "${CURRENT_IMAGE}" "$NESTED_IMAGES_DIR/$IMAGE_NAME"
touch "$NESTED_IMAGES_DIR/$IMAGE_NAME.configured"
# Start the current image again and wait until it is ready
nested_start
fi
fi
else
# Start the nested core vm
nested_start_core_vm_unit "$CURRENT_IMAGE"
fi
}
nested_shutdown() {
# we sometimes have bugs in nested vm's where files that were successfully
# written become empty all of a sudden, so doing a sync here in the VM, and
# another one in the host when done probably helps to avoid that, and at
# least can't hurt anything
remote.exec "sync"
remote.exec "sudo shutdown now" || true
nested_wait_for_no_ssh 120 1
nested_force_stop_vm
tests.systemd wait-for-service -n 30 --wait 1 --state inactive "$NESTED_VM"
sync
}
nested_start() {
nested_save_serial_log
nested_force_start_vm
tests.systemd wait-for-service -n 30 --wait 1 --state active "$NESTED_VM"
nested_wait_for_ssh 300 1
nested_prepare_tools
}
nested_force_restart_vm() {
nested_force_stop_vm
nested_force_start_vm
tests.systemd wait-for-service -n 30 --wait 1 --state active "$NESTED_VM"
}
nested_create_classic_vm() {
local IMAGE_NAME
IMAGE_NAME="$(nested_get_image_name classic)"
mkdir -p "$NESTED_IMAGES_DIR"
if [ ! -f "$NESTED_IMAGES_DIR/$IMAGE_NAME" ]; then
# shellcheck source=tests/lib/image.sh
. "$TESTSLIB"/image.sh
# Get the cloud image
local IMAGE_URL
IMAGE_URL="$(get_image_url_for_vm)"
wget -q -P "$NESTED_IMAGES_DIR" "$IMAGE_URL"
nested_download_image "$IMAGE_URL" "$IMAGE_NAME"
# Prepare the cloud-init configuration and configure image
nested_create_cloud_init_config "$NESTED_ASSETS_DIR/seed"
cloud-localds -H "$(hostname)" "$NESTED_ASSETS_DIR/seed.img" "$NESTED_ASSETS_DIR/seed"
fi
# Save a copy of the image
cp -v "$NESTED_IMAGES_DIR/$IMAGE_NAME" "$NESTED_IMAGES_DIR/$IMAGE_NAME.pristine"
}
nested_start_classic_vm() {
local IMAGE QEMU IMAGE_NAME
QEMU="$(nested_qemu_name)"
IMAGE_NAME="$(nested_get_image_name classic)"
if [ ! -f "$NESTED_IMAGES_DIR/$IMAGE_NAME" ] ; then
cp -v "$NESTED_IMAGES_DIR/$IMAGE_NAME.pristine" "$IMAGE_NAME"
fi
# Give extra disk space for the image
qemu-img resize "$NESTED_IMAGES_DIR/$IMAGE_NAME" +2G
# Now qemu parameters are defined
local PARAM_SMP PARAM_MEM
PARAM_SMP="-smp 1"
# use only 2G of RAM for qemu-nested
if [ "$SPREAD_BACKEND" = "google-nested" ]; then
PARAM_MEM="-m ${NESTED_MEM:-4096}"
PARAM_SMP="-smp ${NESTED_CPUS:-2}"
elif [ "$SPREAD_BACKEND" = "google-nested-dev" ]; then
PARAM_MEM="-m ${NESTED_MEM:-8192}"
PARAM_SMP="-smp ${NESTED_CPUS:-4}"
elif [ "$SPREAD_BACKEND" = "qemu-nested" ]; then
PARAM_MEM="-m ${NESTED_MEM:-2048}"
PARAM_SMP="-smp ${NESTED_CPUS:-1}"
else
echo "unknown spread backend $SPREAD_BACKEND"
exit 1
fi
local PARAM_DISPLAY PARAM_NETWORK PARAM_MONITOR PARAM_USB PARAM_CPU PARAM_CD PARAM_RANDOM PARAM_SNAPSHOT
PARAM_DISPLAY="-nographic"
PARAM_NETWORK="-net nic,model=virtio -net user,hostfwd=tcp::$NESTED_SSH_PORT-:22"
PARAM_MONITOR="-monitor tcp:127.0.0.1:$NESTED_MON_PORT,server=on,wait=off"
PARAM_USB="-usb"
PARAM_CPU=""
PARAM_CD="${NESTED_PARAM_CD:-}"
PARAM_RANDOM="-object rng-random,id=rng0,filename=/dev/urandom -device virtio-rng-pci,rng=rng0"
# TODO: can this be removed? we create a "pristine" copy above?
#PARAM_SNAPSHOT="-snapshot"
PARAM_SNAPSHOT=""
PARAM_EXTRA="${NESTED_PARAM_EXTRA:-}"
# XXX: duplicated from nested core vm
# Set kvm attribute
local ATTR_KVM
ATTR_KVM=""
if nested_is_kvm_enabled; then
ATTR_KVM=",accel=kvm"
# CPU can be defined just when kvm is enabled
PARAM_CPU="-cpu host"
fi
local PARAM_MACHINE PARAM_IMAGE PARAM_SEED PARAM_SERIAL PARAM_BIOS PARAM_TPM
if [[ "$SPREAD_BACKEND" = google-nested* ]]; then
PARAM_MACHINE="-machine ubuntu,accel=kvm"
PARAM_CPU="-cpu host"
elif [ "$SPREAD_BACKEND" = "qemu-nested" ]; then
# check if we have nested kvm
if [ "$(cat /sys/module/kvm_*/parameters/nested)" = "1" ]; then
PARAM_MACHINE="-machine ubuntu${ATTR_KVM}"
else
# and if not reset kvm related parameters
PARAM_MACHINE=""
PARAM_CPU=""
ATTR_KVM=""
fi
else
echo "unknown spread backend $SPREAD_BACKEND"
exit 1
fi
PARAM_IMAGE="-drive file=$NESTED_IMAGES_DIR/$IMAGE_NAME,if=none,id=disk1 -device virtio-blk-pci,drive=disk1,bootindex=1"
PARAM_SEED="-drive file=$NESTED_ASSETS_DIR/seed.img,if=virtio"
# Open port 7777 on the host so that failures in the nested VM (e.g. to
# create users) can be debugged interactively via
# "telnet localhost 7777". Also keeps the logs
#
# XXX: should serial just be logged to stdout so that we just need
# to "journalctl -u $NESTED_VM" to see what is going on ?
if "$QEMU" -version | grep '2\.5'; then
# XXX: remove once we no longer support xenial hosts
PARAM_SERIAL="-serial file:${NESTED_LOGS_DIR}/serial.log"
else
PARAM_SERIAL="-chardev socket,telnet=on,host=localhost,server=on,port=7777,wait=off,id=char0,logfile=${NESTED_LOGS_DIR}/serial.log,logappend=on -serial chardev:char0"
fi
PARAM_BIOS=""
PARAM_TPM=""
# ensure we have a log dir
mkdir -p "$NESTED_LOGS_DIR"
# save logs from previous runs
nested_save_serial_log
# Systemd unit is created, it is important to respect the qemu parameters
# order
tests.systemd create-and-start-unit "$NESTED_VM" "${QEMU} \
${PARAM_SMP} \
${PARAM_CPU} \
${PARAM_MEM} \
${PARAM_SNAPSHOT} \
${PARAM_MACHINE} \
${PARAM_DISPLAY} \
${PARAM_NETWORK} \
${PARAM_BIOS} \
${PARAM_TPM} \
${PARAM_RANDOM} \
${PARAM_IMAGE} \
${PARAM_SEED} \
${PARAM_SERIAL} \
${PARAM_MONITOR} \
${PARAM_USB} \
${PARAM_EXTRA} \
${PARAM_CD} "
if ! nested_wait_vm_ready 60; then
echo "failed to wait for the vm becomes ready to receive connections"
return 1
fi
# Copy tools to be used on tests
nested_prepare_tools
}
nested_destroy_vm() {
tests.systemd stop-unit --remove "$NESTED_VM"
local CURRENT_IMAGE
CURRENT_IMAGE="$NESTED_IMAGES_DIR/$(nested_get_current_image_name)"
rm -f "$CURRENT_IMAGE"
}
nested_status_vm() {
systemctl status "$NESTED_VM" || true
}
remote.exec_as() {
local USER="$1"
local PASSWD="$2"
shift 2
sshpass -p "$PASSWD" ssh -p "$NESTED_SSH_PORT" -o ConnectTimeout=10 -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no "$USER"@localhost "$@"
}
nested_prepare_tools() {
TOOLS_PATH=/writable/test-tools
if ! remote.exec "test -d $TOOLS_PATH" &>/dev/null; then
remote.exec "sudo mkdir -p $TOOLS_PATH"
remote.exec "sudo chown user1:user1 $TOOLS_PATH"
fi
if ! remote.exec "test -e $TOOLS_PATH/retry" &>/dev/null; then
remote.push "$TESTSTOOLS/retry"
remote.exec "mv retry $TOOLS_PATH/retry"
fi
if ! remote.exec "test -e $TOOLS_PATH/not" &>/dev/null; then
remote.push "$TESTSTOOLS/not"
remote.exec "mv not $TOOLS_PATH/not"
fi
if ! remote.exec "test -e $TOOLS_PATH/MATCH" &>/dev/null; then
# shellcheck source=tests/lib/spread-funcs.sh
. "$TESTSLIB"/spread-funcs.sh
echo '#!/bin/bash' > MATCH_FILE
type MATCH | tail -n +2 >> MATCH_FILE
echo 'MATCH "$@"' >> MATCH_FILE
chmod +x MATCH_FILE
remote.push "MATCH_FILE"
remote.exec "mv MATCH_FILE $TOOLS_PATH/MATCH"
rm -f MATCH_FILE
fi
if ! remote.exec "test -e $TOOLS_PATH/NOMATCH" &>/dev/null; then
# shellcheck source=tests/lib/spread-funcs.sh
. "$TESTSLIB"/spread-funcs.sh
echo '#!/bin/bash' > NOMATCH_FILE
type NOMATCH | tail -n +2 >> NOMATCH_FILE
echo 'NOMATCH "$@"' >> NOMATCH_FILE
chmod +x NOMATCH_FILE
remote.push "NOMATCH_FILE"
remote.exec "mv NOMATCH_FILE $TOOLS_PATH/NOMATCH"
rm -f NOMATCH_FILE
fi
if ! remote.exec "grep -qE PATH=.*$TOOLS_PATH /etc/environment"; then
# shellcheck disable=SC2016
REMOTE_PATH="$(remote.exec 'echo $PATH')"
remote.exec "echo PATH=$TOOLS_PATH:$REMOTE_PATH | sudo tee -a /etc/environment"
fi
}
nested_add_tty_chardev() {
local CHARDEV_ID=$1
local CHARDEV_PATH=$2
echo "chardev-add file,path=$CHARDEV_PATH,id=$CHARDEV_ID" | nc -q 0 127.0.0.1 "$NESTED_MON_PORT"
echo "chardev added"
}
nested_remove_chardev() {
local CHARDEV_ID=$1
echo "chardev-remove $CHARDEV_ID" | nc -q 0 127.0.0.1 "$NESTED_MON_PORT"
echo "chardev added"
}
nested_add_usb_serial_device() {
local DEVICE_ID=$1
local CHARDEV_ID=$2
local SERIAL_NUM=$3
echo "device_add usb-serial,chardev=$CHARDEV_ID,id=$DEVICE_ID,serial=$SERIAL_NUM" | nc -q 0 127.0.0.1 "$NESTED_MON_PORT"
echo "device added"
}
nested_del_device() {
local DEVICE_ID=$1
echo "device_del $DEVICE_ID" | nc -q 0 127.0.0.1 "$NESTED_MON_PORT"
echo "device deleted"
}
nested_get_core_revision_for_channel() {
local CHANNEL=$1
remote.exec "snap info core" | awk "/${CHANNEL}: / {print(\$4)}" | sed -e 's/(\(.*\))/\1/'
}
nested_get_core_revision_installed() {
remote.exec "snap info core" | awk "/installed: / {print(\$3)}" | sed -e 's/(\(.*\))/\1/'
}
nested_fetch_spread() {
if [ ! -f "$NESTED_WORK_DIR/spread" ]; then
mkdir -p "$NESTED_WORK_DIR"
curl -s https://storage.googleapis.com/snapd-spread-tests/spread/spread-amd64.tar.gz | tar -xz -C "$NESTED_WORK_DIR"
# make sure spread really exists
test -x "$NESTED_WORK_DIR/spread"
fi
echo "$NESTED_WORK_DIR/spread"
}
nested_build_seed_cdrom() {
local SEED_DIR="$1"
local SEED_NAME="$2"
local LABEL="$3"
shift 3
local ORIG_DIR=$PWD
pushd "$SEED_DIR" || return 1
genisoimage -output "$ORIG_DIR/$SEED_NAME" -volid "$LABEL" -joliet -rock "$@"
popd || return 1
}
nested_wait_for_device_initialized_change() {
local retry=60
local wait=1
while ! remote.exec "snap changes" | MATCH "Done.*Initialize device"; do
retry=$(( retry - 1 ))
if [ $retry -le 0 ]; then
echo "Timed out waiting for device to be fully initialized. Aborting!"
return 1
fi
sleep "$wait"
done
}
nested_check_spread_results() {
SPREAD_LOG=$1
if [ -z "$SPREAD_LOG" ]; then
return 1
fi
if grep -eq "Successful tasks:" "$SPREAD_LOG"; then
if grep -E "Failed (task|suite|project)" "$SPREAD_LOG"; then
return 1
fi
if ! grep -eq "Aborted tasks: 0" "$SPREAD_LOG"; then
return 1
fi
if [ "$EXIT_STATUS" = "0" ]; then
return 0
fi
else
return 1
fi
}