mirror of https://github.com/aya-rs/aya
				
				
				
			Merge pull request #733 from tamird/kernel-test
integration-test: Implement running on VMspull/736/head
						commit
						412a0875b4
					
				| @ -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,166 @@ | ||||
| //! init is the first process started by the kernel.
 | ||||
| //!
 | ||||
| //! This implementation creates the minimal mounts required to run BPF programs, runs all binaries
 | ||||
| //! in /bin, prints a final message ("init: success|failure"), and powers off the machine.
 | ||||
| 
 | ||||
| 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 how = nix::sys::reboot::RebootMode::RB_POWER_OFF; | ||||
|     let _: std::convert::Infallible = nix::sys::reboot::reboot(how) | ||||
|         .unwrap_or_else(|err| panic!("reboot({how:?}) failed: {err:?}")); | ||||
| } | ||||
| @ -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