mirror of https://github.com/aya-rs/aya
integration-test: Implement running on VMs
This allows integration tests to be run using qemu at an arbitrary kernel version. Note that before this change we were only testing fedora 38 which used 6.2. Now we're testing 6.1 kernels. The tests all pass. Older kernel versions were attempted, but the tests don't all pass. Later work can add more kernel versions to test.pull/638/head
parent
3376bbd81f
commit
e836865088
@ -0,0 +1,10 @@
|
||||
[package]
|
||||
name = "init"
|
||||
version = "0.1.0"
|
||||
authors = ["Tamir Duberstein <tamird@gmail.com>"]
|
||||
edition = "2021"
|
||||
publish = false
|
||||
|
||||
[dependencies]
|
||||
anyhow = { workspace = true, features = ["std"] }
|
||||
nix = { workspace = true, features = ["fs", "mount", "reboot"] }
|
@ -0,0 +1,161 @@
|
||||
use anyhow::Context as _;
|
||||
|
||||
#[derive(Debug)]
|
||||
struct Errors(Vec<anyhow::Error>);
|
||||
|
||||
impl std::fmt::Display for Errors {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
let Self(errors) = self;
|
||||
for (i, error) in errors.iter().enumerate() {
|
||||
if i != 0 {
|
||||
writeln!(f)?;
|
||||
}
|
||||
write!(f, "{:?}", error)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for Errors {}
|
||||
|
||||
fn run() -> anyhow::Result<()> {
|
||||
const RXRXRX: nix::sys::stat::Mode = nix::sys::stat::Mode::empty()
|
||||
.union(nix::sys::stat::Mode::S_IRUSR)
|
||||
.union(nix::sys::stat::Mode::S_IXUSR)
|
||||
.union(nix::sys::stat::Mode::S_IRGRP)
|
||||
.union(nix::sys::stat::Mode::S_IXGRP)
|
||||
.union(nix::sys::stat::Mode::S_IROTH)
|
||||
.union(nix::sys::stat::Mode::S_IXOTH);
|
||||
|
||||
struct Mount {
|
||||
source: &'static str,
|
||||
target: &'static str,
|
||||
fstype: &'static str,
|
||||
flags: nix::mount::MsFlags,
|
||||
data: Option<&'static str>,
|
||||
target_mode: Option<nix::sys::stat::Mode>,
|
||||
}
|
||||
|
||||
for Mount {
|
||||
source,
|
||||
target,
|
||||
fstype,
|
||||
flags,
|
||||
data,
|
||||
target_mode,
|
||||
} in [
|
||||
Mount {
|
||||
source: "proc",
|
||||
target: "/proc",
|
||||
fstype: "proc",
|
||||
flags: nix::mount::MsFlags::empty(),
|
||||
data: None,
|
||||
target_mode: Some(RXRXRX),
|
||||
},
|
||||
Mount {
|
||||
source: "sysfs",
|
||||
target: "/sys",
|
||||
fstype: "sysfs",
|
||||
flags: nix::mount::MsFlags::empty(),
|
||||
data: None,
|
||||
target_mode: Some(RXRXRX),
|
||||
},
|
||||
Mount {
|
||||
source: "debugfs",
|
||||
target: "/sys/kernel/debug",
|
||||
fstype: "debugfs",
|
||||
flags: nix::mount::MsFlags::empty(),
|
||||
data: None,
|
||||
target_mode: None,
|
||||
},
|
||||
Mount {
|
||||
source: "bpffs",
|
||||
target: "/sys/fs/bpf",
|
||||
fstype: "bpf",
|
||||
flags: nix::mount::MsFlags::empty(),
|
||||
data: None,
|
||||
target_mode: None,
|
||||
},
|
||||
] {
|
||||
match target_mode {
|
||||
None => {
|
||||
// Must exist.
|
||||
let nix::sys::stat::FileStat { st_mode, .. } = nix::sys::stat::stat(target)
|
||||
.with_context(|| format!("stat({target}) failed"))?;
|
||||
let s_flag = nix::sys::stat::SFlag::from_bits_truncate(st_mode);
|
||||
|
||||
if !s_flag.contains(nix::sys::stat::SFlag::S_IFDIR) {
|
||||
anyhow::bail!("{target} is not a directory");
|
||||
}
|
||||
}
|
||||
Some(target_mode) => {
|
||||
// Must not exist.
|
||||
nix::unistd::mkdir(target, target_mode)
|
||||
.with_context(|| format!("mkdir({target}) failed"))?;
|
||||
}
|
||||
}
|
||||
nix::mount::mount(Some(source), target, Some(fstype), flags, data).with_context(|| {
|
||||
format!("mount({source}, {target}, {fstype}, {flags:?}, {data:?}) failed")
|
||||
})?;
|
||||
}
|
||||
|
||||
// By contract we run everything in /bin and assume they're rust test binaries.
|
||||
//
|
||||
// If the user requested command line arguments, they're named init.arg={}.
|
||||
|
||||
// Read kernel parameters from /proc/cmdline. They're space separated on a single line.
|
||||
let cmdline = std::fs::read_to_string("/proc/cmdline")
|
||||
.with_context(|| "read_to_string(/proc/cmdline) failed")?;
|
||||
let args = cmdline
|
||||
.split_whitespace()
|
||||
.filter_map(|parameter| {
|
||||
parameter
|
||||
.strip_prefix("init.arg=")
|
||||
.map(std::ffi::OsString::from)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
// Iterate files in /bin.
|
||||
let read_dir = std::fs::read_dir("/bin").context("read_dir(/bin) failed")?;
|
||||
let errors = read_dir
|
||||
.filter_map(|entry| {
|
||||
match (|| {
|
||||
let entry = entry.context("read_dir(/bin) failed")?;
|
||||
let path = entry.path();
|
||||
let status = std::process::Command::new(&path)
|
||||
.args(&args)
|
||||
.status()
|
||||
.with_context(|| format!("failed to execute {}", path.display()))?;
|
||||
|
||||
if status.code() == Some(0) {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(anyhow::anyhow!("{} failed: {status:?}", path.display()))
|
||||
}
|
||||
})() {
|
||||
Ok(()) => None,
|
||||
Err(err) => Some(err),
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
if errors.is_empty() {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(Errors(errors).into())
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
match run() {
|
||||
Ok(()) => {
|
||||
println!("init: success");
|
||||
}
|
||||
Err(err) => {
|
||||
println!("{err:?}");
|
||||
println!("init: failure");
|
||||
}
|
||||
}
|
||||
let _: std::convert::Infallible =
|
||||
nix::sys::reboot::reboot(nix::sys::reboot::RebootMode::RB_POWER_OFF)
|
||||
.expect("reboot failed");
|
||||
}
|
@ -1,264 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
VERBOSITY=0
|
||||
TEMP_D=""
|
||||
DEF_DISK_FORMAT="raw"
|
||||
DEF_FILESYSTEM="iso9660"
|
||||
CR="
|
||||
"
|
||||
|
||||
error() { echo "$@" 1>&2; }
|
||||
fail() { [ $# -eq 0 ] || error "$@"; exit 1; }
|
||||
|
||||
Usage() {
|
||||
cat <<EOF
|
||||
Usage: ${0##*/} [ options ] output user-data [meta-data]
|
||||
|
||||
Create a disk for cloud-init to utilize nocloud
|
||||
|
||||
options:
|
||||
-h | --help show usage
|
||||
-d | --disk-format D disk format to output. default: raw
|
||||
can be anything supported by qemu-img or
|
||||
tar, tar-seed-local, tar-seed-net
|
||||
-H | --hostname H set hostname in metadata to H
|
||||
-f | --filesystem F filesystem format (vfat or iso), default: iso9660
|
||||
|
||||
-i | --interfaces F write network interfaces file into metadata
|
||||
-N | --network-config F write network config file to local datasource
|
||||
-m | --dsmode M add 'dsmode' ('local' or 'net') to the metadata
|
||||
default in cloud-init is 'net', meaning network is
|
||||
required.
|
||||
-V | --vendor-data F vendor-data file
|
||||
-v | --verbose increase verbosity
|
||||
|
||||
Note, --dsmode, --hostname, and --interfaces are incompatible
|
||||
with metadata.
|
||||
|
||||
Example:
|
||||
* cat my-user-data
|
||||
#cloud-config
|
||||
password: passw0rd
|
||||
chpasswd: { expire: False }
|
||||
ssh_pwauth: True
|
||||
* echo "instance-id: \$(uuidgen || echo i-abcdefg)" > my-meta-data
|
||||
* ${0##*/} my-seed.img my-user-data my-meta-data
|
||||
* kvm -net nic -net user,hostfwd=tcp::2222-:22 \\
|
||||
-drive file=disk1.img,if=virtio -drive file=my-seed.img,if=virtio
|
||||
* ssh -p 2222 ubuntu@localhost
|
||||
EOF
|
||||
}
|
||||
|
||||
bad_Usage() { Usage 1>&2; [ $# -eq 0 ] || error "$@"; exit 1; }
|
||||
cleanup() {
|
||||
[ -z "${TEMP_D}" -o ! -d "${TEMP_D}" ] || rm -Rf "${TEMP_D}"
|
||||
}
|
||||
|
||||
debug() {
|
||||
local level=${1}; shift;
|
||||
[ "${level}" -gt "${VERBOSITY}" ] && return
|
||||
error "${@}"
|
||||
}
|
||||
|
||||
has_cmd() {
|
||||
command -v "$1" >/dev/null 2>&1
|
||||
}
|
||||
|
||||
short_opts="hH:i:d:f:m:N:o:V:v"
|
||||
long_opts="disk-format:,dsmode:,filesystem:,help,hostname:,interfaces:,"
|
||||
long_opts="${long_opts}network-config:,output:,vendor-data:,verbose"
|
||||
getopt_out=$(getopt -n "${0##*/}" \
|
||||
-o "${short_opts}" -l "${long_opts}" -- "$@") &&
|
||||
eval set -- "${getopt_out}" ||
|
||||
bad_Usage
|
||||
|
||||
## <<insert default variables here>>
|
||||
output=""
|
||||
userdata=""
|
||||
metadata=""
|
||||
vendordata=""
|
||||
filesystem=""
|
||||
diskformat=$DEF_DISK_FORMAT
|
||||
interfaces=_unset
|
||||
dsmode=""
|
||||
hostname=""
|
||||
ncname="network-config"
|
||||
|
||||
|
||||
while [ $# -ne 0 ]; do
|
||||
cur=${1}; next=${2};
|
||||
case "$cur" in
|
||||
-h|--help) Usage ; exit 0;;
|
||||
-d|--disk-format) diskformat=$next; shift;;
|
||||
-f|--filesystem) filesystem=$next; shift;;
|
||||
-H|--hostname) hostname=$next; shift;;
|
||||
-i|--interfaces) interfaces=$next; shift;;
|
||||
-N|--network-config) netcfg=$next; shift;;
|
||||
-m|--dsmode) dsmode=$next; shift;;
|
||||
-v|--verbose) VERBOSITY=$((${VERBOSITY}+1));;
|
||||
-V|--vendor-data) vendordata="$next";;
|
||||
--) shift; break;;
|
||||
esac
|
||||
shift;
|
||||
done
|
||||
|
||||
## check arguments here
|
||||
## how many args do you expect?
|
||||
echo $1
|
||||
echo $2
|
||||
echo $3
|
||||
[ $# -ge 2 ] || bad_Usage "must provide output, userdata"
|
||||
[ $# -le 3 ] || bad_Usage "confused by additional args"
|
||||
|
||||
output=$1
|
||||
userdata=$2
|
||||
metadata=$3
|
||||
|
||||
if [ -n "$metadata" ]; then
|
||||
[ "$interfaces" = "_unset" -a -z "$dsmode" -a -z "$hostname" ] ||
|
||||
fail "metadata is incompatible with:" \
|
||||
"--interfaces, --hostname, --dsmode"
|
||||
fi
|
||||
|
||||
case "$diskformat" in
|
||||
tar|tar-seed-local|tar-seed-net)
|
||||
if [ "${filesystem:-tar}" != "tar" ]; then
|
||||
fail "diskformat=tar is incompatible with filesystem"
|
||||
fi
|
||||
filesystem="$diskformat"
|
||||
;;
|
||||
tar*)
|
||||
fail "supported 'tar' formats are tar, tar-seed-local, tar-seed-net"
|
||||
esac
|
||||
|
||||
if [ -z "$filesystem" ]; then
|
||||
filesystem="$DEF_FILESYSTEM"
|
||||
fi
|
||||
if [ "$filesystem" = "iso" ]; then
|
||||
filesystem="iso9660"
|
||||
fi
|
||||
|
||||
case "$filesystem" in
|
||||
tar*)
|
||||
has_cmd tar ||
|
||||
fail "missing 'tar'. Required for --filesystem=$filesystem";;
|
||||
vfat)
|
||||
has_cmd mkfs.vfat ||
|
||||
fail "missing 'mkfs.vfat'. Required for --filesystem=vfat."
|
||||
has_cmd mcopy ||
|
||||
fail "missing 'mcopy'. Required for --filesystem=vfat."
|
||||
;;
|
||||
iso9660)
|
||||
has_cmd mkisofs ||
|
||||
fail "missing 'mkisofs'. Required for --filesystem=iso9660."
|
||||
;;
|
||||
*) fail "unknown filesystem $filesystem";;
|
||||
esac
|
||||
|
||||
case "$diskformat" in
|
||||
tar*|raw) :;;
|
||||
*) has_cmd "qemu-img" ||
|
||||
fail "missing 'qemu-img'. Required for --disk-format=$diskformat."
|
||||
esac
|
||||
|
||||
[ "$interfaces" = "_unset" -o -r "$interfaces" ] ||
|
||||
fail "$interfaces: not a readable file"
|
||||
|
||||
TEMP_D=$(mktemp -d "${TMPDIR:-/tmp}/${0##*/}.XXXXXX") ||
|
||||
fail "failed to make tempdir"
|
||||
trap cleanup EXIT
|
||||
|
||||
files=( "${TEMP_D}/user-data" "${TEMP_D}/meta-data" )
|
||||
if [ -n "$metadata" ]; then
|
||||
cp "$metadata" "$TEMP_D/meta-data" || fail "$metadata: failed to copy"
|
||||
else
|
||||
instance_id="iid-local01"
|
||||
iface_data=""
|
||||
[ "$interfaces" != "_unset" ] &&
|
||||
iface_data=$(sed ':a;N;$!ba;s/\n/\\n/g' "$interfaces")
|
||||
|
||||
# write json formatted user-data (json is a subset of yaml)
|
||||
mdata=""
|
||||
for kv in "instance-id:$instance_id" "local-hostname:$hostname" \
|
||||
"interfaces:${iface_data}" "dsmode:$dsmode"; do
|
||||
key=${kv%%:*}
|
||||
val=${kv#*:}
|
||||
[ -n "$val" ] || continue
|
||||
mdata="${mdata:+${mdata},${CR}}\"$key\": \"$val\""
|
||||
done
|
||||
printf "{\n%s\n}\n" "$mdata" > "${TEMP_D}/meta-data"
|
||||
fi
|
||||
|
||||
if [ -n "$netcfg" ]; then
|
||||
cp "$netcfg" "${TEMP_D}/$ncname" ||
|
||||
fail "failed to copy network config"
|
||||
files[${#files[@]}]="$TEMP_D/$ncname"
|
||||
fi
|
||||
|
||||
if [ -n "$vendordata" ]; then
|
||||
cp "$vendordata" "${TEMP_D}/vendor-data" ||
|
||||
fail "failed to copy vendor data"
|
||||
files[${#files[@]}]="$TEMP_D/vendor-data"
|
||||
fi
|
||||
|
||||
files_rel=( )
|
||||
for f in "${files[@]}"; do
|
||||
files_rel[${#files_rel[@]}]="${f#${TEMP_D}/}"
|
||||
done
|
||||
|
||||
if [ "$userdata" = "-" ]; then
|
||||
cat > "$TEMP_D/user-data" || fail "failed to read from stdin"
|
||||
else
|
||||
cp "$userdata" "$TEMP_D/user-data" || fail "$userdata: failed to copy"
|
||||
fi
|
||||
|
||||
## alternatively, create a vfat filesystem with same files
|
||||
img="$TEMP_D/seed-data"
|
||||
tar_opts=( --owner=root --group=root )
|
||||
|
||||
case "$filesystem" in
|
||||
tar)
|
||||
tar "${tar_opts[@]}" -C "${TEMP_D}" -cf "$img" "${files_rel[@]}" ||
|
||||
fail "failed to create tarball of ${files_rel[*]}"
|
||||
;;
|
||||
tar-seed-local|tar-seed-net)
|
||||
if [ "$filesystem" = "tar-seed-local" ]; then
|
||||
path="var/lib/cloud/seed/nocloud"
|
||||
else
|
||||
path="var/lib/cloud/seed/nocloud-net"
|
||||
fi
|
||||
mkdir -p "${TEMP_D}/${path}" ||
|
||||
fail "failed making path for seed files"
|
||||
mv "${files[@]}" "${TEMP_D}/$path" ||
|
||||
fail "failed moving files"
|
||||
tar "${tar_opts[@]}" -C "${TEMP_D}" -cf "$img" "${path}" ||
|
||||
fail "failed to create tarball with $path"
|
||||
;;
|
||||
iso9660)
|
||||
mkisofs -output "$img" -volid cidata \
|
||||
-joliet -rock "${files[@]}" > "$TEMP_D/err" 2>&1 ||
|
||||
{ cat "$TEMP_D/err" 1>&2; fail "failed to mkisofs"; }
|
||||
;;
|
||||
vfat)
|
||||
truncate -s 128K "$img" || fail "failed truncate image"
|
||||
out=$(mkfs.vfat -n cidata "$img" 2>&1) ||
|
||||
{ error "failed: mkfs.vfat -n cidata $img"; error "$out"; }
|
||||
mcopy -oi "$img" "${files[@]}" :: ||
|
||||
fail "failed to copy user-data, meta-data to img"
|
||||
;;
|
||||
esac
|
||||
|
||||
[ "$output" = "-" ] && output="$TEMP_D/final"
|
||||
if [ "${diskformat#tar}" != "$diskformat" -o "$diskformat" = "raw" ]; then
|
||||
cp "$img" "$output" ||
|
||||
fail "failed to copy image to $output"
|
||||
else
|
||||
qemu-img convert -f raw -O "$diskformat" "$img" "$output" ||
|
||||
fail "failed to convert to disk format $diskformat"
|
||||
fi
|
||||
|
||||
[ "$output" != "$TEMP_D/final" ] || { cat "$output" && output="-"; } ||
|
||||
fail "failed to write to -"
|
||||
|
||||
debug 1 "wrote ${output} with filesystem=$filesystem and diskformat=$diskformat"
|
||||
# vi: ts=4 noexpandtab
|
@ -1,241 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -ex
|
||||
|
||||
if [ "$(uname -s)" = "Darwin" ]; then
|
||||
PATH="$(dirname "$(brew list gnu-getopt | grep "bin/getopt$")"):$PATH"
|
||||
export PATH
|
||||
fi
|
||||
|
||||
AYA_SOURCE_DIR="$(realpath "$(dirname "$0")"/..)"
|
||||
|
||||
# Temporary directory for tests to use.
|
||||
AYA_TMPDIR="${AYA_SOURCE_DIR}/.tmp"
|
||||
|
||||
# Directory for VM images
|
||||
AYA_IMGDIR=${AYA_TMPDIR}
|
||||
|
||||
if [ -z "${AYA_BUILD_TARGET}" ]; then
|
||||
AYA_BUILD_TARGET=$(rustc -vV | sed -n 's|host: ||p')
|
||||
fi
|
||||
|
||||
AYA_HOST_ARCH=$(uname -m)
|
||||
if [ "${AYA_HOST_ARCH}" = "arm64" ]; then
|
||||
AYA_HOST_ARCH="aarch64"
|
||||
fi
|
||||
|
||||
if [ -z "${AYA_GUEST_ARCH}" ]; then
|
||||
AYA_GUEST_ARCH="${AYA_HOST_ARCH}"
|
||||
fi
|
||||
|
||||
if [ "${AYA_GUEST_ARCH}" = "aarch64" ]; then
|
||||
if [ -z "${AARCH64_UEFI}" ]; then
|
||||
AARCH64_UEFI="$(brew list qemu -1 -v | grep edk2-aarch64-code.fd)"
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ -z "$AYA_MUSL_TARGET" ]; then
|
||||
AYA_MUSL_TARGET=${AYA_GUEST_ARCH}-unknown-linux-musl
|
||||
fi
|
||||
|
||||
# Test Image
|
||||
if [ -z "${AYA_TEST_IMAGE}" ]; then
|
||||
AYA_TEST_IMAGE="fedora38"
|
||||
fi
|
||||
|
||||
case "${AYA_TEST_IMAGE}" in
|
||||
fedora*) AYA_SSH_USER="fedora";;
|
||||
centos*) AYA_SSH_USER="centos";;
|
||||
esac
|
||||
|
||||
download_images() {
|
||||
mkdir -p "${AYA_IMGDIR}"
|
||||
case $1 in
|
||||
fedora37)
|
||||
if [ ! -f "${AYA_IMGDIR}/fedora37.${AYA_GUEST_ARCH}.qcow2" ]; then
|
||||
IMAGE="Fedora-Cloud-Base-37-1.7.${AYA_GUEST_ARCH}.qcow2"
|
||||
IMAGE_URL="https://download.fedoraproject.org/pub/fedora/linux/releases/37/Cloud/${AYA_GUEST_ARCH}/images"
|
||||
echo "Downloading: ${IMAGE}, this may take a while..."
|
||||
curl -o "${AYA_IMGDIR}/fedora37.${AYA_GUEST_ARCH}.qcow2" -sSL "${IMAGE_URL}/${IMAGE}"
|
||||
fi
|
||||
;;
|
||||
fedora38)
|
||||
if [ ! -f "${AYA_IMGDIR}/fedora38.${AYA_GUEST_ARCH}.qcow2" ]; then
|
||||
IMAGE="Fedora-Cloud-Base-38_Beta-1.3.${AYA_GUEST_ARCH}.qcow2"
|
||||
IMAGE_URL="https://fr2.rpmfind.net/linux/fedora/linux/releases/test/38_Beta/Cloud/${AYA_GUEST_ARCH}/images"
|
||||
echo "Downloading: ${IMAGE}, this may take a while..."
|
||||
curl -o "${AYA_IMGDIR}/fedora38.${AYA_GUEST_ARCH}.qcow2" -sSL "${IMAGE_URL}/${IMAGE}"
|
||||
fi
|
||||
;;
|
||||
centos8)
|
||||
if [ ! -f "${AYA_IMGDIR}/centos8.${AYA_GUEST_ARCH}.qcow2" ]; then
|
||||
IMAGE="CentOS-8-GenericCloud-8.4.2105-20210603.0.${AYA_GUEST_ARCH}.qcow2"
|
||||
IMAGE_URL="https://cloud.centos.org/centos/8/${AYA_GUEST_ARCH}/images"
|
||||
echo "Downloading: ${IMAGE}, this may take a while..."
|
||||
curl -o "${AYA_IMGDIR}/centos8.${AYA_GUEST_ARCH}.qcow2" -sSL "${IMAGE_URL}/${IMAGE}"
|
||||
fi
|
||||
;;
|
||||
*)
|
||||
echo "$1 is not a recognized image name"
|
||||
return 1
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
start_vm() {
|
||||
download_images "${AYA_TEST_IMAGE}"
|
||||
# prepare config
|
||||
cat > "${AYA_TMPDIR}/metadata.yaml" <<EOF
|
||||
instance-id: iid-local01
|
||||
local-hostname: test
|
||||
EOF
|
||||
|
||||
if [ ! -f "${AYA_TMPDIR}/test_rsa" ]; then
|
||||
ssh-keygen -t rsa -b 4096 -f "${AYA_TMPDIR}/test_rsa" -N "" -C "" -q
|
||||
pub_key=$(cat "${AYA_TMPDIR}/test_rsa.pub")
|
||||
fi
|
||||
|
||||
if [ ! -f "${AYA_TMPDIR}/ssh_config" ]; then
|
||||
cat > "${AYA_TMPDIR}/ssh_config" <<EOF
|
||||
StrictHostKeyChecking=no
|
||||
UserKnownHostsFile=/dev/null
|
||||
GlobalKnownHostsFile=/dev/null
|
||||
EOF
|
||||
fi
|
||||
|
||||
cat > "${AYA_TMPDIR}/user-data.yaml" <<EOF
|
||||
#cloud-config
|
||||
ssh_authorized_keys:
|
||||
- ${pub_key}
|
||||
EOF
|
||||
|
||||
"$AYA_SOURCE_DIR"/test/cloud-localds "${AYA_TMPDIR}/seed.img" "${AYA_TMPDIR}/user-data.yaml" "${AYA_TMPDIR}/metadata.yaml"
|
||||
case "${AYA_GUEST_ARCH}" in
|
||||
x86_64)
|
||||
QEMU=qemu-system-x86_64
|
||||
machine="q35"
|
||||
cpu="qemu64"
|
||||
nr_cpus="$(nproc --all)"
|
||||
if [ "${AYA_HOST_ARCH}" = "${AYA_GUEST_ARCH}" ]; then
|
||||
if [ -c /dev/kvm ]; then
|
||||
machine="${machine},accel=kvm"
|
||||
cpu="host"
|
||||
elif [ "$(uname -s)" = "Darwin" ]; then
|
||||
machine="${machine},accel=hvf"
|
||||
cpu="host"
|
||||
fi
|
||||
fi
|
||||
;;
|
||||
aarch64)
|
||||
QEMU=qemu-system-aarch64
|
||||
machine="virt"
|
||||
cpu="cortex-a57"
|
||||
uefi=("-drive" "file=${AARCH64_UEFI},if=pflash,format=raw,readonly=on")
|
||||
if [ "${AYA_HOST_ARCH}" = "${AYA_GUEST_ARCH}" ]; then
|
||||
if [ -c /dev/kvm ]; then
|
||||
machine="${machine},accel=kvm"
|
||||
cpu="host"
|
||||
nr_cpus="$(nproc --all)"
|
||||
elif [ "$(uname -s)" = "Darwin" ]; then
|
||||
machine="${machine},accel=hvf,highmem=off"
|
||||
cpu="cortex-a72"
|
||||
# nrpoc --all on apple silicon returns the two extra fancy
|
||||
# cores and then qemu complains that nr_cpus > actual_cores
|
||||
nr_cpus=8
|
||||
fi
|
||||
fi
|
||||
;;
|
||||
*)
|
||||
echo "${AYA_GUEST_ARCH} is not supported"
|
||||
return 1
|
||||
;;
|
||||
esac
|
||||
|
||||
if [ ! -f "${AYA_IMGDIR}/vm.qcow2" ]; then
|
||||
echo "Creating VM image"
|
||||
qemu-img create -F qcow2 -f qcow2 -o backing_file="${AYA_IMGDIR}/${AYA_TEST_IMAGE}.${AYA_GUEST_ARCH}.qcow2" "${AYA_IMGDIR}/vm.qcow2" || return 1
|
||||
else
|
||||
echo "Reusing existing VM image"
|
||||
fi
|
||||
$QEMU \
|
||||
-machine "${machine}" \
|
||||
-cpu "${cpu}" \
|
||||
-m 3G \
|
||||
-smp "${nr_cpus}" \
|
||||
-display none \
|
||||
-monitor none \
|
||||
-daemonize \
|
||||
-pidfile "${AYA_TMPDIR}/vm.pid" \
|
||||
-device virtio-net-pci,netdev=net0 \
|
||||
-netdev user,id=net0,hostfwd=tcp::2222-:22 \
|
||||
"${uefi[@]}" \
|
||||
-drive if=virtio,format=qcow2,file="${AYA_IMGDIR}/vm.qcow2" \
|
||||
-drive if=virtio,format=raw,file="${AYA_TMPDIR}/seed.img" || return 1
|
||||
|
||||
trap cleanup_vm EXIT
|
||||
echo "Waiting for SSH on port 2222..."
|
||||
retry=0
|
||||
max_retries=300
|
||||
while ! ssh -q -F "${AYA_TMPDIR}/ssh_config" -o ConnectTimeout=1 -i "${AYA_TMPDIR}/test_rsa" "${AYA_SSH_USER}"@localhost -p 2222 echo "Hello VM"; do
|
||||
retry=$((retry+1))
|
||||
if [ ${retry} -gt ${max_retries} ]; then
|
||||
echo "Unable to connect to VM"
|
||||
return 1
|
||||
fi
|
||||
sleep 1
|
||||
done
|
||||
|
||||
echo "VM launched"
|
||||
exec_vm uname -a
|
||||
echo "Enabling testing repositories"
|
||||
exec_vm sudo dnf config-manager --set-enabled updates-testing
|
||||
exec_vm sudo dnf config-manager --set-enabled updates-testing-modular
|
||||
}
|
||||
|
||||
scp_vm() {
|
||||
local=$1
|
||||
remote=$(basename "$1")
|
||||
scp -q -F "${AYA_TMPDIR}/ssh_config" \
|
||||
-i "${AYA_TMPDIR}/test_rsa" \
|
||||
-P 2222 "${local}" \
|
||||
"${AYA_SSH_USER}@localhost:${remote}"
|
||||
}
|
||||
|
||||
rsync_vm() {
|
||||
rsync -a -e "ssh -p 2222 -F ${AYA_TMPDIR}/ssh_config -i ${AYA_TMPDIR}/test_rsa" "$1" "$AYA_SSH_USER"@localhost:
|
||||
}
|
||||
|
||||
exec_vm() {
|
||||
ssh -q -F "${AYA_TMPDIR}/ssh_config" \
|
||||
-i "${AYA_TMPDIR}/test_rsa" \
|
||||
-p 2222 \
|
||||
"${AYA_SSH_USER}"@localhost \
|
||||
"$@"
|
||||
}
|
||||
|
||||
stop_vm() {
|
||||
if [ -f "${AYA_TMPDIR}/vm.pid" ]; then
|
||||
echo "Stopping VM forcefully"
|
||||
kill -9 "$(cat "${AYA_TMPDIR}/vm.pid")"
|
||||
rm "${AYA_TMPDIR}/vm.pid"
|
||||
fi
|
||||
}
|
||||
|
||||
cleanup_vm() {
|
||||
if ! stop_vm; then
|
||||
rm -f "${AYA_IMGDIR}/vm.qcow2"
|
||||
fi
|
||||
}
|
||||
|
||||
start_vm
|
||||
trap cleanup_vm EXIT
|
||||
|
||||
# make sure we always use fresh sources (also see comment at the end)
|
||||
rsync_vm "$*"
|
||||
|
||||
exec_vm "find $* -type f -executable -print0 | xargs -0 -I {} sudo {} --test-threads=1"
|
||||
|
||||
# we rm and sync but it doesn't seem to work reliably - I guess we could sleep a
|
||||
# few seconds after but ain't nobody got time for that. Instead we also rm
|
||||
# before rsyncing.
|
||||
exec_vm "rm -rf $*; sync"
|
Loading…
Reference in New Issue