NetworkManager/tools/nm-in-vm

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

359 lines
11 KiB
Text
Raw Normal View History

#!/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 '<path>')
if [[ $libvirt_pool_path_xml =~ \<path\>(.*)\</path\> ]]; 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}
datadir="$BASEDIR_NM/tools/nm-guest-data"
##############################################################################
do_build() {
local t=$'\t'
local nm_ci_build_args
local nm_ci_install_args
local extra_pkgs
local install_pkgs
local install_files
local gen_files=(
"bin-nm-env-prepare.sh:/usr/bin/nm-env-prepare.sh"
"bin-nm-deploy.sh:/usr/bin/nm-deploy.sh"
"etc-motd-vm:/etc/motd"
"etc-bashrc.my:/etc/bashrc.my"
"nm-90-my.conf:/etc/NetworkManager/conf.d/90-my.conf"
"nm-95-user.conf:/etc/NetworkManager/conf.d/95-user.conf"
"home-bash_history:/root/.bash_history"
"home-gdbinit:/root/.gdbinit"
"home-gdb_history:/root/.gdb_history"
"home-behaverc:/root/.behaverc"
"systemd-20-nm.override:/etc/systemd/system/NetworkManager.service.d/20-nm.override"
)
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_build_args=(
--mkdir "$BASEDIR_NM_CI"
--link "$BASEDIR_NM_CI:/NetworkManager-ci"
--append-line "/etc/fstab:/NM_CI${t}$BASEDIR_NM_CI${t}9p${t}trans=virtio,rw,_netdev${t}0${t}0"
)
nm_ci_install_args=(
--filesystem "$BASEDIR_NM_CI,/NM_CI"
)
fi
if [[ $OS_VERSION =~ fedora* || $OS_VERSION =~ centos* ]]; then
extra_pkgs=(bash-completion bind-utils ccache clang-tools-extra cryptsetup cscope \'dbus\*\'
dhcp-client dhcp-relay dhcp-server dnsmasq dracut-network ethtool firewalld gcc gdb
glibc-langpack-pl hostapd intltool iproute ipsec-tools iputils iscsi-initiator-utils
iw ldns libreswan libselinux-utils libyaml-devel logrotate lvm2 mdadm mlocate net-tools
NetworkManager NetworkManager-openvpn NetworkManager-ovs NetworkManager-ppp
NetworkManager-pptp NetworkManager-strongswan NetworkManager-team NetworkManager-vpnc
NetworkManager-wifi nfs-utils nispor nmap-ncat nmstate nss-tools openvpn
\'openvswitch2\*\' perl-IO-Pty-Easy perl-IO-Tty procps psmisc python3-behave
python3-black python3-devel python3-netaddr python3-pip python3-pyte python3-pyyaml
qemu-kvm radvd rp-pppoe scsi-target-utils strace systemd tcpdump tcpreplay tuned
/usr/bin/debuginfo-install /usr/bin/pytest /usr/bin/python vim wireguard-tools
wireshark-cli)
install_pkgs=(
--run "$BASEDIR_NM/contrib/fedora/REQUIRED_PACKAGES"
--run-command "dnf install -y --skip-broken ${extra_pkgs[*]}"
--run-command "dnf debuginfo-install -y --skip-broken NetworkManager \
\$(ldd /usr/sbin/NetworkManager \
| sed -n 's/.* => \(.*\) (0x[0-9A-Fa-f]*)\$/\1/p' \
| xargs -n1 readlink -f)"
)
elif [[ $OS_VERSION =~ debian* || $OS_VERSION =~ ubuntu* ]]; then
install_pkgs=(
--run "$BASEDIR_NM/contrib/debian/REQUIRED_PACKAGES"
)
fi
install_files=(--upload "$BASEDIR_NM/contrib/scripts/NM-log:/usr/bin/NM-log")
for f in "${gen_files[@]}"; do
gen_file "${f%:*}"
install_files+=(--upload "$datadir/data-$f")
done
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 '<none>')"
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 \
--append-line "/etc/ssh/sshd_config:PermitRootLogin yes" \
--mkdir "$BASEDIR_NM" \
--link "$BASEDIR_NM:/NetworkManager" \
--append-line "/etc/fstab:/NM${t}$BASEDIR_NM${t}9p${t}trans=virtio,rw,_netdev${t}0${t}0" \
"${nm_ci_build_args[@]}" \
--update \
"${install_pkgs[@]}" \
--run-command "pip3 install --user behave_html_formatter" \
--mkdir "/etc/systemd/system/NetworkManager.service.d" \
"${install_files[@]}" \
--write "/var/lib/NetworkManager/secret_key:nm-in-container-secret-key" \
--chmod "700:/var/lib/NetworkManager" \
--chmod "600:/var/lib/NetworkManager/secret_key" \
--edit "/etc/systemd/journald.conf:s/.*RateLimitBurst=.*/RateLimitBurst=0/" \
--delete "/etc/NetworkManager/system-connections/*" \
--append-line "/etc/bashrc:. /etc/bashrc.my" \
--run-command "updatedb"
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_install_args[@]}" \
--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
}
gen_file() {
sed "s|{{BASEDIR_NM}}|$BASEDIR_NM|g" "$datadir/$1.in" > "$datadir/data-$1"
if [[ $1 =~ bin-* ]]; then
chmod 755 "$datadir/data-$1"
else
chmod 644 "$datadir/data-$1"
fi
}
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 "$@"