#!/bin/bash set -e ############################################################################### # Script to create a virtual machine for testing NetworkManager. # # Commands: # - build: build a new VM, named "$VM" ("nm"). # - run: start the VM. # - exec: run bash inside the VM, connecting via ssh (this is the default). # - stop: stop the VM. # - reexec: stop and exec. # - clean: stop the VM and delete the image. # # Commands exec and reexec accepts extra arguments, which are the command to # execute in the VM instead of opening an ssh session. # # NetworkManager directories: # # The NetworkManager root directory is mounted in the VM as a filesystem share. # You can run `make install` and run tests. # # Create a symlink ./.git/NetworkManager-ci, to share the CI directory too. # # Required packages: # # Your host needs libvirt, libvirt-nss and guestfs-tools. To access the VM with # `ssh root@$VM`, configure /etc/nsswitch.conf as explained in # https://libvirt.org/nss.html (otherwise, `nm-in-vm exec` won't work, either). # # Prepare for testing: # # There is a script nm-env-prepare.sh to generate a net1 interface for testing. # Currently NM-ci requires a working eth1, so use this before running a CI test: # `nm-env-prepare.sh --prefix eth -i 1 && sleep 1 && nmcli device connect eth1` # # Additional VMs: # # By default, the VM named 'nm' is created, but additional ones can be created: # $ VM=nm2 nm-in-vm build # $ VM=nm2 nm-in-vm exec # $ VM=nm2 nm-in-vm stop # # Choosing a different OS: # # By default Fedora is used, but you can choose a different OS. Most from the # list `virt-builder --list` will work. # $ OS_VERSION=debian-12 nm-in-vm build # $ nm-in-vm exec # $ nm-in-vm stop ############################################################################### # Check for libvirt if ! (command -v virsh && command -v virt-builder && command -v virt-install) &>/dev/null; then echo "libvirt and guestfs-tools are required" >&2 exit 1 fi # set defaults if user didn't define these values VM=${VM:="nm"} OS_VERSION=${OS_VERSION:=} RAM=${RAM:=2048} IMAGE_SIZE=${IMAGE_SIZE:=10G} ROOT_PASSWORD=${ROOT_PASSWORD:=nm} LIBVIRT_POOL=${LIBVIRT_POOL:=default} # only useful if BASEDIR_VM_IMAGE is empty BASEDIR_VM_IMAGE=${BASEDIR_VM_IMAGE:=} BASEDIR_NM=${BASEDIR_NM:=} BASEDIR_NM_CI=${BASEDIR_NM_CI:=} HOST_BRIDGE=${HOST_BRIDGE:=virbr0} SSH_LOG_LEVEL=${SSH_LOG_LEVEL:=ERROR} if [[ -z $OS_VERSION ]]; then # if running Fedora, select same version, else select latest Fedora if grep -q "^ID=fedora$" /etc/os-release 2>/dev/null ; then OS_VERSION="$(sed -n 's/^VERSION_ID=\([0-9]\+\)$/fedora-\1/p' /etc/os-release)" else OS_VERSION=$(virt-builder --list | grep '^fedora' | sort | tail -n 1 | cut -d" " -f 1) fi fi if [[ -z $BASEDIR_NM ]]; then BASEDIR_NM="$(readlink -f "$(dirname -- "$BASH_SOURCE")/..")" fi if [[ -z $BASEDIR_NM_CI && -d "$BASEDIR_NM/.git/NetworkManager-ci" ]]; then BASEDIR_NM_CI="$(readlink -f "$BASEDIR_NM/.git/NetworkManager-ci")" fi if [[ -z $BASEDIR_VM_IMAGE ]]; then libvirt_pool_path_xml=$(virsh pool-dumpxml $LIBVIRT_POOL | grep -F '') if [[ $libvirt_pool_path_xml =~ \(.*)\ ]]; then BASEDIR_VM_IMAGE=${BASH_REMATCH[1]} else BASEDIR_VM_IMAGE=$BASEDIR_NM fi fi # compute some values that depends on user selectable variables basedir_vm_image=$(readlink -f "$BASEDIR_VM_IMAGE") vm_image_file="$VM.qcow2" os_variant=${OS_VERSION//-/} # virt-install --os-variant value, deduced from OS_VERSION os_variant=${os_variant/centosstream/centos-stream} ############################################################################## do_build() { local t t=$'\t' if vm_is_installed; then echo "The virtual machine '$VM' is already installed, skiping build" >&2 return 0 fi if vm_image_exists; then echo "The image '$basedir_vm_image/$vm_image_file' already exists, skiping build" >&2 return 0 fi if [[ -n $BASEDIR_NM_CI ]]; then nm_ci_mkdir=("--mkdir" "$BASEDIR_NM_CI") nm_ci_fstab=("--append-line" "/etc/fstab:/NM_CI${t}$BASEDIR_NM_CI${t}9p${t}trans=virtio,rw,_netdev${t}0${t}0") nm_ci_fs=("--filesystem" "$BASEDIR_NM_CI,/NM_CI") fi echo "Creating VM" echo " - VM NAME: $VM" echo " - OS VERSION: $OS_VERSION" echo " - SIZE: $IMAGE_SIZE" echo " - RAM: $RAM" echo " - ROOT PASSWORD: $ROOT_PASSWORD" echo " - IMAGE PATH: $basedir_vm_image/$vm_image_file" echo " - NM DIR: $BASEDIR_NM" echo " - NM CI DIR: $([[ -n $BASEDIR_NM_CI ]] && echo "$BASEDIR_NM_CI" || echo '')" echo " - HOST BRIDGE: $HOST_BRIDGE" virt-builder "$OS_VERSION" \ --output "$basedir_vm_image/$vm_image_file" \ --size "$IMAGE_SIZE" \ --format qcow2 \ --arch x86_64 \ --hostname "$VM" \ --root-password password:nm \ --ssh-inject root \ --mkdir "$BASEDIR_NM" \ --append-line "/etc/fstab:/NM${t}$BASEDIR_NM${t}9p${t}trans=virtio,rw,_netdev${t}0${t}0" \ "${nm_ci_mkdir[@]}" \ "${nm_ci_fstab[@]}" \ --update virt-install \ --name "$VM" \ --ram "$RAM" \ --disk "path=$basedir_vm_image/$vm_image_file,format=qcow2" \ --os-variant "$os_variant" \ --filesystem "$BASEDIR_NM,/NM" \ "${nm_ci_fs[@]}" \ --network "bridge=$HOST_BRIDGE" \ --import \ --autoconsole none \ --noreboot } do_clean() { vm_is_running && virsh shutdown "$VM" &>/dev/null vm_is_installed && virsh undefine "$VM" &>/dev/null rm -f "$basedir_vm_image/$vm_image_file" } do_run() { vm_is_installed || do_build vm_is_running && return 0 virsh start "$VM" >&2 } do_exec() { do_run local failed=0 while ! ping -c 1 "$VM" &>/dev/null; do failed=$((failed + 1)) (( failed < 15 )) || die "Timeout trying to ping the VM" sleep 1 done ssh \ -o StrictHostKeyChecking=no \ -o UserKnownHostsFile=/dev/null \ -o LogLevel="$SSH_LOG_LEVEL" \ "root@$VM" "$@" } do_reexec() { vm_is_running && do_stop local waited=0 while vm_is_running; do waited=$((waited + 1)) (( waited < 30 )) || die "Timeout waiting for VM shutdown" sleep 1 done do_exec "$@" } do_stop() { vm_is_running && virsh shutdown "$VM" >&2 } ############################################################################### vm_image_exists() { [[ -f "$basedir_vm_image/$vm_image_file" ]] || return 1 } vm_is_installed() { virsh list --all --name | grep --fixed-strings --line-regexp "$VM" &>/dev/null || return 1 } vm_is_running() { virsh list --name | grep --fixed-strings --line-regexp "$VM" &>/dev/null || return 1 } usage() { echo "nm-in-vm [-h|--help] build|run|exec|stop|reexec|clean" } help() { usage echo awk '/^####*$/ { if (on) exit; on=-1; } !/^####*$/ { if (on) print(substr($0,3)) }' "$BASH_SOURCE" echo } die() { echo "$1" >&2 exit 1 } ############################################################################### cmd= while (( $# > 0 )); do case "$1" in build|run|exec|reexec|stop|clean) cmd="$1" shift ;; -h|--help) help exit 0 ;; --) shift break ;; *) [[ $cmd != "" ]] && break echo "Invalid argument '$1'" >&2 echo $(usage) >&2 exit 1 ;; esac done if [[ $cmd == "" ]]; then cmd=exec fi if [[ $UID == 0 ]]; then die "cannot run as root" fi if [[ $cmd != exec && $cmd != reexec && $# != 0 ]]; then die "Extra arguments are only allowed with exec|reexec command" fi do_$cmd "$@"