On Sat, Jan 23, 2021 at 12:44:44AM +0000, KP Singh wrote: > The script runs the BPF selftests locally on the same kernel image > as they would run post submit in the BPF continuous integration > framework. > > The goal of the script is to allow contributors to run selftests locally > in the same environment to check if their changes would end up breaking > the BPF CI and reduce the back-and-forth between the maintainers and the > developers. > > Signed-off-by: KP Singh <kpsingh@xxxxxxxxxx> great! thanks for this Tested-by: Jiri Olsa <jolsa@xxxxxxxxxx> jirka > --- > tools/testing/selftests/bpf/run_in_vm.sh | 353 +++++++++++++++++++++++ > 1 file changed, 353 insertions(+) > create mode 100755 tools/testing/selftests/bpf/run_in_vm.sh > > diff --git a/tools/testing/selftests/bpf/run_in_vm.sh b/tools/testing/selftests/bpf/run_in_vm.sh > new file mode 100755 > index 000000000000..09bb9705acb3 > --- /dev/null > +++ b/tools/testing/selftests/bpf/run_in_vm.sh > @@ -0,0 +1,353 @@ > +#!/bin/bash > +# SPDX-License-Identifier: GPL-2.0 > + > +set -u > +set -e > + > +QEMU_BINARY="${QEMU_BINARY:="qemu-system-x86_64"}" > +X86_BZIMAGE="arch/x86/boot/bzImage" > +DEFAULT_COMMAND="./test_progs" > +MOUNT_DIR="mnt" > +ROOTFS_IMAGE="root.img" > +OUTPUT_DIR="$HOME/.bpf_selftests" > +KCONFIG_URL="https://raw.githubusercontent.com/libbpf/libbpf/master/travis-ci/vmtest/configs/latest.config" > +INDEX_URL="https://raw.githubusercontent.com/libbpf/libbpf/master/travis-ci/vmtest/configs/INDEX" > +NUM_COMPILE_JOBS="$(nproc)" > + > +usage() > +{ > + cat <<EOF > +Usage: $0 [-k] [-i] [-d <output_dir>] -- [<command>] > + > +<command> is the command you would normally run when you are in > +tools/testing/selftests/bpf. e.g: > + > + $0 -- ./test_progs -t test_lsm > + > +If no command is specified, "${DEFAULT_COMMAND}" will be run by > +default. > + > +If you build your kernel using KBUILD_OUTPUT= or O= options, these > +can be passed as environment variables to the script: > + > + O=<path_relative_to_cwd> $0 -- ./test_progs -t test_lsm > + > +or > + > + KBUILD_OUTPUT=<path_relative_to_cwd> $0 -- ./test_progs -t test_lsm > + > +Options: > + > + -k) "Keep the kernel", i.e. don't recompile the kernel if it exists. > + -i) Update the rootfs image with a newer version. > + -d) Update the output directory (default: ${OUTPUT_DIR}) > + -j) Number of jobs for compilation, similar to -j in make > + (default: ${NUM_COMPILE_JOBS}) > +EOF > +} > + > +unset URLS > +populate_url_map() > +{ > + if ! declare -p URLS &> /dev/null; then > + # URLS contain the mapping from file names to URLs where > + # those files can be downloaded from. > + declare -gA URLS > + while IFS=$'\t' read -r name url; do > + URLS["$name"]="$url" > + done < <(curl -Lsf ${INDEX_URL}) > + fi > + echo "${URLS[*]}" > +} > + > +download() > +{ > + local file="$1" > + > + if [[ ! -v URLS[$file] ]]; then > + echo "$file not found" >&2 > + return 1 > + fi > + > + echo "Downloading $file..." >&2 > + curl -Lf "${URLS[$file]}" "${@:2}" > +} > + > +newest_rootfs_version() > +{ > + { > + for file in "${!URLS[@]}"; do > + if [[ $file =~ ^libbpf-vmtest-rootfs-(.*)\.tar\.zst$ ]]; then > + echo "${BASH_REMATCH[1]}" > + fi > + done > + } | sort -rV | head -1 > +} > + > +download_rootfs() > +{ > + local rootfsversion="$1" > + local dir="$2" > + > + if ! which zstd &> /dev/null; then > + echo 'Could not find "zstd" on the system, please install zstd' > + exit 1 > + fi > + > + download "libbpf-vmtest-rootfs-$rootfsversion.tar.zst" | > + zstd -d | sudo tar -C "$dir" -x > +} > + > +recompile_kernel() > +{ > + local kernel_checkout="$1" > + local make_command="$2" > + > + cd "${kernel_checkout}" > + > + ${make_command} olddefconfig > + ${make_command} > +} > + > +mount_image() > +{ > + local rootfs_img="${OUTPUT_DIR}/${ROOTFS_IMAGE}" > + local mount_dir="${OUTPUT_DIR}/${MOUNT_DIR}" > + > + sudo mount -o loop "${rootfs_img}" "${mount_dir}" > +} > + > +unmount_image() > +{ > + local mount_dir="${OUTPUT_DIR}/${MOUNT_DIR}" > + > + sudo umount "${mount_dir}" &> /dev/null > +} > + > +update_selftests() > +{ > + local kernel_checkout="$1" > + local selftests_dir="${kernel_checkout}/tools/testing/selftests/bpf" > + > + cd "${selftests_dir}" > + ${make_command} > + > + # Mount the image and copy the selftests to the image. > + mount_image > + sudo rm -rf "${mount_dir}/root/bpf" > + sudo cp -r "${selftests_dir}" "${mount_dir}/root" > + unmount_image > +} > + > +update_init_script() > +{ > + local init_script_dir="${OUTPUT_DIR}/${MOUNT_DIR}/etc/rcS.d" > + local init_script="${init_script_dir}/S50-startup" > + local command="$1" > + local log_file="$2" > + > + mount_image > + > + if [[ ! -d "${init_script_dir}" ]]; then > + cat <<EOF > +Could not find ${init_script_dir} in the mounted image. > +This likely indicates a bad rootfs image, Please download > +a new image by passing "-i" to the script > +EOF > + exit 1 > + > + fi > + > + cat <<EOF | sudo tee "${init_script}" > +#!/bin/bash > + > +{ > + > + cd /root/bpf > + echo ${command} > + ${command} > +} 2>&1 | tee /root/${log_file} > +poweroff -f > +EOF > + > + sudo chmod a+x "${init_script}" > + unmount_image > +} > + > +create_vm_image() > +{ > + local rootfs_img="${OUTPUT_DIR}/${ROOTFS_IMAGE}" > + local mount_dir="${OUTPUT_DIR}/${MOUNT_DIR}" > + > + rm -rf "${rootfs_img}" > + touch "${rootfs_img}" > + chattr +C "${rootfs_img}" >/dev/null 2>&1 || true > + > + truncate -s 2G "${rootfs_img}" > + mkfs.ext4 -q "${rootfs_img}" > + > + mount_image > + download_rootfs "$(newest_rootfs_version)" "${mount_dir}" > + unmount_image > +} > + > +run_vm() > +{ > + local kernel_bzimage="$1" > + local rootfs_img="${OUTPUT_DIR}/${ROOTFS_IMAGE}" > + > + if ! which "${QEMU_BINARY}" &> /dev/null; then > + cat <<EOF > +Could not find ${QEMU_BINARY} > +Please install qemu or set the QEMU_BINARY environment variable. > +EOF > + exit 1 > + fi > + > + ${QEMU_BINARY} \ > + -nodefaults \ > + -display none \ > + -serial mon:stdio \ > + -cpu kvm64 \ > + -enable-kvm \ > + -smp 4 \ > + -m 2G \ > + -drive file="${rootfs_img}",format=raw,index=1,media=disk,if=virtio,cache=none \ > + -kernel "${kernel_bzimage}" \ > + -append "root=/dev/vda rw console=ttyS0,115200" > +} > + > +copy_logs() > +{ > + local mount_dir="${OUTPUT_DIR}/${MOUNT_DIR}" > + local log_file="${mount_dir}/root/$1" > + > + mount_image > + sudo cp ${log_file} "${OUTPUT_DIR}" > + sudo rm -f ${log_file} > + unmount_image > +} > + > +is_rel_path() > +{ > + local path="$1" > + > + [[ ${path:0:1} != "/" ]] > +} > + > +main() > +{ > + local script_dir="$(cd -P -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd -P)" > + local kernel_checkout=$(realpath "${script_dir}"/../../../../) > + local log_file="$(date +"bpf_selftests.%Y-%m-%d_%H-%M-%S.log")" > + # By default the script searches for the kernel in the checkout directory but > + # it also obeys environment variables O= and KBUILD_OUTPUT= > + local kernel_bzimage="${kernel_checkout}/${X86_BZIMAGE}" > + local command="${DEFAULT_COMMAND}" > + local kernel_recompile="yes" > + local update_image="no" > + > + while getopts 'hkid:j:' opt; do > + case ${opt} in > + k) > + kernel_recompile="no" > + ;; > + i) > + update_image="yes" > + ;; > + d) > + OUTPUT_DIR="$OPTARG" > + ;; > + j) > + NUM_COMPILE_JOBS="$OPTARG" > + ;; > + h) > + usage > + exit 0 > + ;; > + \? ) > + echo "Invalid Option: -$OPTARG" > + usage > + exit 1 > + ;; > + : ) > + echo "Invalid Option: -$OPTARG requires an argument" > + usage > + exit 1 > + ;; > + esac > + done > + shift $((OPTIND -1)) > + > + if [[ $# -eq 0 ]]; then > + echo "No command specified, will run ${DEFAULT_COMMAND} in the vm" > + else > + command="$@" > + fi > + > + local kconfig_file="${OUTPUT_DIR}/latest.config" > + local make_command="make -j ${NUM_COMPILE_JOBS} KCONFIG_CONFIG=${kconfig_file}" > + > + # Figure out where the kernel is being built. > + # O takes precedence over KBUILD_OUTPUT. > + if [[ "${O:=""}" != "" ]]; then > + if is_rel_path "${O}"; then > + O="$(realpath "${PWD}/${O}")" > + fi > + kernel_bzimage="${O}/${X86_BZIMAGE}" > + make_command="${make_command} O=${O}" > + elif [[ "${KBUILD_OUTPUT:=""}" != "" ]]; then > + if is_rel_path "${KBUILD_OUTPUT}"; then > + KBUILD_OUTPUT="$(realpath "${PWD}/${KBUILD_OUTPUT}")" > + fi > + kernel_bzimage="${KBUILD_OUTPUT}/${X86_BZIMAGE}" > + make_command="${make_command} KBUILD_OUTPUT=${KBUILD_OUTPUT}" > + fi > + > + populate_url_map > + > + local rootfs_img="${OUTPUT_DIR}/${ROOTFS_IMAGE}" > + local mount_dir="${OUTPUT_DIR}/${MOUNT_DIR}" > + > + echo "Output directory: ${OUTPUT_DIR}" > + > + mkdir -p "${OUTPUT_DIR}" > + mkdir -p "${mount_dir}" > + curl -Lf "${KCONFIG_URL}" -o "${kconfig_file}" > + > + if [[ "${kernel_recompile}" == "no" && ! -f "${kernel_bzimage}" ]]; then > + echo "Kernel image not found in ${kernel_bzimage}, kernel will be recompiled" > + kernel_recompile="yes" > + fi > + > + if [[ "${kernel_recompile}" == "yes" ]]; then > + recompile_kernel "${kernel_checkout}" "${make_command}" > + fi > + > + if [[ "${update_image}" == "no" && ! -f "${rootfs_img}" ]]; then > + echo "rootfs image not found in ${rootfs_img}" > + update_image="yes" > + fi > + > + if [[ "${update_image}" == "yes" ]]; then > + create_vm_image > + fi > + > + update_selftests "${kernel_checkout}" "${make_command}" > + update_init_script "${command}" "${log_file}" > + run_vm "${kernel_bzimage}" > + copy_logs "${log_file}" > + echo "Logs saved in ${OUTPUT_DIR}/${log_file}" > +} > + > +catch() > +{ > + local exit_code=$1 > + > + unmount_image > + exit ${exit_code} > +} > + > +trap 'catch "$?"' EXIT > + > +main "$@" > -- > 2.30.0.280.ga3ce27912f-goog >