changeset 474:57f253106ed6

Implement fbhyve, a management system that runs bhyve virtual machines within tmux sessions
author Franz Glasner <fzglas.hg@dom66.de>
date Tue, 27 Aug 2024 21:47:57 +0200
parents c3125616d0ec
children 827371176fec
files .hgignore Makefile docs/man/man8/fbhyve.rst files/fbhyve.in pkg-plist tests/fbhyve/fbhyve_pc03.rc.conf.test tests/fbhyve/prepare-pc03.sh tests/fbhyve/vm_xxx_pc03.config
diffstat 8 files changed, 258 insertions(+), 126 deletions(-) [+]
line wrap: on
line diff
--- a/.hgignore	Mon Aug 26 17:22:26 2024 +0200
+++ b/.hgignore	Tue Aug 27 21:47:57 2024 +0200
@@ -4,3 +4,4 @@
 ^docs/_build/
 ^docs/_venv.*/
 ^_venv.*/
+^tests/fbhyve/bootvars.fd
--- a/Makefile	Mon Aug 26 17:22:26 2024 +0200
+++ b/Makefile	Tue Aug 27 21:47:57 2024 +0200
@@ -26,9 +26,13 @@
 USE_RC_SUBR=	fbhyve fwireguard
 
 SUB_LIST=	SIMPLEVERSIONTAG="${SIMPLEVERSIONTAG}" \
-		FWIREGUARD_ETCDIR="${FWIREGUARD_ETCDIR}"
+		FWIREGUARD_ETCDIR="${FWIREGUARD_ETCDIR}" \
+		FBHYVE_ETCDIR="${FBHYVE_ETCDIR}" \
+		FBHYVE_RUNDIR="${FBHYVE_RUNDIR}"
 
-PLIST_SUB=	FWIREGUARD_ETCDIR="${FWIREGUARD_ETCDIR}"
+PLIST_SUB=	FWIREGUARD_ETCDIR="${FWIREGUARD_ETCDIR}" \
+		FBHYVE_ETCDIR="${FBHYVE_ETCDIR}" \
+	=	FBHYVE_RUNDIR="${FBHYVE_RUNDIR}"
 
 .include <bsd.port.options.mk>
 
@@ -49,6 +53,9 @@
 
 FWIREGUARD_ETCDIR?=	"${PREFIX}/etc/fwireguard"
 
+FBHYVE_ETCDIR?=	"${PREFIX}/etc/fbhyve"
+FBHYVE_RUNDIR?=	/var/run/fbhyve
+
 do-extract:
 	${MKDIR} ${WRKSRC}/bin
 	${MKDIR} ${WRKSRC}/sbin
@@ -111,6 +118,8 @@
 	${INSTALL_DATA} ${WRKSRC}/share/examples/${PORTNAME}/${_exf} ${STAGEDIR}${EXAMPLESDIR}
 .endfor
 	${MKDIR} ${STAGEDIR}${FWIREGUARD_ETCDIR}
+	${MKDIR} ${STAGEDIR}${FBHYVE_ETCDIR}
+	${MKDIR} ${STAGEDIR}${FBHYVE_RUNDIR}
 
 post-install-DOCS-on:
 .for _mp in ${MANPAGES5:R}
--- a/docs/man/man8/fbhyve.rst	Mon Aug 26 17:22:26 2024 +0200
+++ b/docs/man/man8/fbhyve.rst	Tue Aug 27 21:47:57 2024 +0200
@@ -6,35 +6,60 @@
 Synopsis
 --------
 
-**service fbhyve** [ **start** | **stop** | **poll** | **status** ] [**name**]...
+**service fbhyve** [ **start** | **stop** | **restart** | **poll** | **status** ] [**name**]...
 
 
 Description
 -----------
 
-An :file:`rc.d` script to start :manpage:`bhyve(8)` virtual machines
+An :file:`rc.d` script to start :manpage:`bhyve(8)` virtual machines.
+
+Every VM is lives its own dedicated :manpage:`tmux(1)` session and has
+its console windows therein. The sessions have names that are derived from
+the name of the corresponding VM.
 
 
 Configuration Variables
 ~~~~~~~~~~~~~~~~~~~~~~~
 
 `fbhyve_enable`
+  Act as default for all listed VMs.
   Default: NO
 
 `fbhyve_list`
-  The name of VMs to start to (aka. "profiles" or "sessions")
+  The list of names of VMs to start to (aka. "profiles" or "sessions").
+  Default: empty
+
+`fbhyve_configdir`
+  Directory where by default the configuration files for all VMs live.
+  Default: :file:`/usr/local/etc/fbhyve`
+
+`fbhyve_tmux_session_prefix`
+  All tmux session names will have this prefix string. The effective session
+  name will have the VM's name appended.
+
+Each VM that is listed in `fbhyve_list` can be configured individually with:
+
+`fbhyve_<vm>_enable`
+  Enable or disable the virtual machine.
+  Default: ``$fbhyve_enable``
+
+`fbhyve_<vm>_config`
+  The :manpage:`bhyve_config(5)` style configuration file with configuration
+  variables for the virtual machine.
+  Default: ``${fbhyve_configdir}/<vm>.conf``
 
 
 Files
 -----
 
-- :file:`/usr/local/etc/fbhyve`
+- :file:`/var/run/fbhyve`
 
-    Default-directory where all the configuration files with relative
-    filenames are looked up.
+    Directory where some runtime data and PID files are stored.
 
 
 See also
 --------
 
-:manpage:`bhyve(8)`, :manpage:`rc.conf(5)`, :manpage:`tmux(1)`
+:manpage:`bhyve(8)`, :manpage:`bhyve_config(5)`, :manpage:`rc.conf(5)`,
+:manpage:`tmux(1)`
--- a/files/fbhyve.in	Mon Aug 26 17:22:26 2024 +0200
+++ b/files/fbhyve.in	Tue Aug 27 21:47:57 2024 +0200
@@ -1,7 +1,8 @@
 #!/bin/sh
+# -*- indent-tabs-mode: nil -*-
 
 # PROVIDE: fbhyve
-# REQUIRE: LOGIN
+# REQUIRE: LOGIN FILESYSTEMS
 # KEYWORD: shutdown nojail
 #
 
@@ -9,140 +10,145 @@
 
 #
 # Add the following lines to /etc/rc.conf to enable bhyve:
+#
 # fbhyve_enable (bool):  Set to "NO" by default.
-#                        Set it to "YES" to enable bhyve
-# bhyve_profiles (str): Set to "" by default.
-#                       Define your profiles here.
-# bhyve_tapdev (str):   Set to "tap0" by default.
-#                       Set to the tap(4) device to use.
-# bhyve_diskdev (str):  Must be set, no default.
-#                       Set to the disk device to use.
-# bhyve_ncpu (int):     Set to 1 by default.
-#                       Set to the number of CPUs for the VM.
-# bhyve_memsize (int):  Set to 512 by default.
-#                       Set to the number of MB of memory for the VM.
+#                        Acts as default for all listed VMs.
+#                        Set it to "YES" to enable bhyve.
+# fbhyve_list (str):     Set to "" by default.
+#                        Define the names of your VMs here.
+# fbhyve_tmux_session_prefix (str):  All tmux session names will have this
+#                                    prefix string.
+# fbhyve_configdir (str): Where by default config files for all VMs live.
+#                         Default: $PREFIX/etc/fbhyve
+#
+#
+# fbhyve_<vm>_enable (bool):  Allow to enable or disable a specific VM.
+#                             Set to $fbhyve_enable by default.
+# fbhyve_<vm>_config (str):   The bhyve configuration file to use to.
+#                             The default is $fbhyve_configdir/<vm>.conf
+#
 
 . /etc/rc.subr
 
 name="fbhyve"
-rcvar=fbhyve_enable
+desc="Manage system bhyve virtual machines"
+rcvar="fbhyve_enable"
 
-start_precmd="bhyve_prestart"
-status_cmd="bhyve_status"
-poll_cmd="bhyve_poll"
-stop_cmd="bhyve_stop"
-_session=$name
-command="/usr/local/bin/tmux"
-procname="-sh"
+start_precmd="fbhyve_pre_start"
+stop_postcmd="fbhyve_post_stop"
+status_cmd="fbhyve_status"
 
-[ -z "$bhyve_tapdev" ]  && bhyve_tapdev="tap0"
-[ -z "$bhyve_diskdev" ] && bhyve_diskdev="none"
-[ -z "$bhyve_ncpu" ]    && bhyve_ncpu="1"
-[ -z "$bhyve_memsize" ] && bhyve_memsize="512"
 
 load_rc_config $name
 
-: {fbhyve_enable:="NO"}
-: {fbhyve_configdir:="%%PREFIX%%/etc/fbhyve"}
+: ${fbhyve_enable:="NO"}
+: ${fbhyve_list=}
+: ${fbhyve_tmux_session_prefix:="${name}_"}
+: ${fbhyve_configdir:="%%FBHYVE_ETCDIR%%"}
+
+
+_fbhyve_vm_exists() {
+    local _p
+    for _p in ${fbhyve_list}; do
+        [ "${_p}" = "$1" ] && return 0;
+    done
+    return 1
+}
+
+
+if [ $# -eq 2 ]; then
+    _vm="$2"
+    if ! _fbhyve_vm_exists "${_vm}"; then
+        echo "ERROR: no VM named \`${_vm}' in \`fbhyve_list'" 1>&2
+        exit 1
+    fi
+    echo "-- VM: ${_vm} --"
+    _session="${fbhyve_tmux_session_prefix}${_vm}"
+    _window="${_session}_console"
+    eval fbhyve_enable="\${fbhyve_${_vm}_enable:-${fbhyve_enable}}"
+    eval fbhyve_config="\${fbhyve_${_vm}_config:-\"${fbhyve_configdir}/${_vm}.conf\"}"
+else
+    _ec=0
+    _swap=$*; shift; _vmarglist=$*
+    _vmlist=${_vmarglist:-${fbhyve_list}}
+    set -- ${_swap}
+    for _vm in ${_vmlist}; do
+        "$0" "$1" "${_vm}"
+        _vmec=$?
+        if [ ${_vmec} -gt ${_ec} ]; then
+            _ec=${_vmec}
+        fi
+    done
+    exit ${_ec}
+fi
+
+_rundir="%%FBHYVE_RUNDIR%%"
+pidfile="${_rundir}/${_vm}.pid"
+procname="bhyve:"         # something like bhyve: <vmname> (bhyve)
+
+required_dirs="${_rundir}"
+required_files="${fbhyve_config}"
+
+command="%%LOCALBASE%%/bin/tmux"
+
+command_args="new-session -ds ${_session} -n ${_window} \"sh -c 'echo \\\$\\\$ >\\\"${pidfile}\\\"; /usr/sbin/bhyve -k \\\"${fbhyve_config}\\\" \\\"${_vm}\\\"'\""
 
 
-if [ -n "$2" ]; then
-	profile="$2"
-	_session="${_session}_${profile}"
-	if [ "x${bhyve_profiles}" != "x" ]; then
-		eval fbhyve_enable="\${${_session}_enable:-${fbhyve_enable}}"
-		eval bhyve_tapdev="\${${_session}_tapdev:-${bhyve_tapdev}}"
-		eval bhyve_diskdev="\${${_session}_diskdev:-${bhyve_diskdev}}"
-		eval bhyve_ncpu="\${${_session}_ncpu:-${bhyve_ncpu}}"
-		eval bhyve_memsize="\${${_session}_memsize:-${bhyve_memsize}}"
-	else
-		echo "$0: extra argument ignored"
-	fi
-else
-	if [ "x${bhyve_profiles}" != "x" -a "x$1" != "x" ]; then
-		for profile in ${bhyve_profiles}; do
-			eval _enable="\${bhyve_${profile}_enable}"
-			case "x${_enable:-${fbhyve_enable}}" in
-			x|x[Nn][Oo]|x[Nn][Oo][Nn][Ee])
-				continue
-				;;
-			x[Yy][Ee][Ss])
-				;;
-			*)
-				if test -z "$_enable"; then
-					_var=fbhyve_enable
-				else
-					_var=bhyve_"${profile}"_enable
-				fi
-				echo "Bad value" \
-				    "'${_enable:-${fbhyve_enable}}'" \
-				    "for ${_var}. " \
-				    "Profile ${profile} skipped."
-				continue
-				;;
-			esac
-			echo "===> bhyve profile: ${profile}"
-			/usr/local/etc/rc.d/bhyve $1 ${profile}
-			retcode="$?"
-			if [ "0${retcode}" -ne 0 ]; then
-				failed="${profile} (${retcode}) ${failed:-}"
-			else
-				success="${profile} ${success:-}"
-			fi
-		done
-		exit 0
-	fi
-	profile=$name
-fi
+fbhyve_status()
+{
+    local _pid _rc
+
+    _rc=0
+    _pid=$(check_pidfile "$pidfile" "$procname")
+    if [ -n "${_pid}" ]; then
+        echo "VM ${_vm} is running as pid $_pid."
+    else
+        echo "VM ${_vm} is not running."
+        _rc=1
+    fi
 
-pidfile="/var/run/${_session}.pid"
+    if ${command} has-session -t ${_session} 2>/dev/null; then
+	echo "tmux session ${_session} exists."
+        if [ ${_rc} -gt 0 ]; then
+            _rc=2
+        fi
+    else
+	echo "tmux session ${_session} does not exist."
+        if [ ${_rc} -gt 0 ]; then
+            _rc=2
+        fi
+    fi
+    return ${_rc}
+}
 
 
-bhyve_prestart()
-{
-	case ${bhyve_diskdev} in
-	[Nn][Oo][Nn][Ee] | '')
-		echo "No ${_session}_diskdev set. Quitting." 1>&2
-		return 1;
-		;;
-	esac
-	if [ ! -c "${bhyve_diskdev}" -a ! -f "${bhyve_diskdev}" ]; then
-		echo "${bhyve_diskdev} doesn't exist or is not suitable as a diskdev" 1>&2
-		return 1;
-	fi
-}
-
-bhyve_status()
-{
-	if ${command} has-session -t ${_session} 2>/dev/null; then
-		echo "${_session} is running."
-	else
-		echo "${_session} is not running."
-		return 1
-	fi
+fbhyve_pre_start() {
+    if ! load_kld -m vmm vmm.ko; then
+        echo "ERROR: Cannot load kernel module \`vmm'" 1>&2
+        return 1
+    fi
+    if [ -e "/dev/vmm/${_vm}" ]; then
+        echo "ERROR: VM \`${_vm}' already created in the VM monitor" 1>&2
+        return 1
+    fi
+    if ${command} has-session -t "${_session}" 2>/dev/null; then
+        echo "ERROR: tmux session \`${_session}' already exists" 1>&2
+        return 1
+    fi
+    return 0
 }
 
-bhyve_poll()
-{
-	echo -n "Waiting for session: ${_session}"
-	while ${command} has-session -t ${_session} 2>/dev/null; do
-		sleep 1
-	done
-	echo
+
+fbhyve_post_stop() {
+    if [ -e "/dev/vmm/${_vm}" ]; then
+        /usr/sbin/bhyvectl --vm="${_vm}" --destroy
+    fi
+    if ${command} has-session -t "${_session}" 2>/dev/null; then
+        ${command} kill-session -t "${_session}"
+    fi
+    rm -f "${pidfile}"
+    return 0
 }
 
-bhyve_stop()
-{
-	if ${command} has-session -t ${_session} 2>/dev/null; then
-		echo "Stopping ${_session}."
-		${command} kill-session -t ${_session}
-		while ${command} has-session -t ${_session} 2>/dev/null; do
-			sleep 1
-		done
-	fi
-	rm -f ${pidfile}
-}
-
-command_args="new-session -ds ${_session} \"sh -c 'echo \\\$PPID >${pidfile}; while true; do /usr/sbin/bhyvectl --vm=${_session} --destroy; /usr/sbin/bhyveload -m ${bhyve_memsize} -d ${bhyve_diskdev} ${_session} && /usr/sbin/bhyve -c ${bhyve_ncpu} -m ${bhyve_memsize} -AI -H -P -g 0 -s 0:0,hostbridge -s 1:0,virtio-net,${bhyve_tapdev} -s 2:0,virtio-blk,${bhyve_diskdev} -s 31,lpc -l com1,stdio ${_session} || break; done'\""
 
 run_rc_command "$1"
--- a/pkg-plist	Mon Aug 26 17:22:26 2024 +0200
+++ b/pkg-plist	Tue Aug 27 21:47:57 2024 +0200
@@ -46,3 +46,5 @@
 @comment DIRECTORIES
 @dir %%ETCDIR%%
 @dir %%FWIREGUARD_ETCDIR%%
+@dir %%FBHYVE_ETCDIR%%
+@dir %%FBHYVE_RUNDIR%%
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/fbhyve/fbhyve_pc03.rc.conf.test	Tue Aug 27 21:47:57 2024 +0200
@@ -0,0 +1,13 @@
+#!/bin/sh
+
+_TESTHOME="/home/fag/work/ports/sysutils/local-bsdtools"
+
+#ln -sf "${_TESTHOME}/tests/fbhyve/rc.conf.test /etc/rc.conf.d/fbhyve
+
+#rc_debug=YES
+
+fbhyve_enable=YES
+
+fbhyve_list="xxx yyy"
+
+fbhyve_xxx_config="${_TESTHOME}/tests/fbhyve/vm_xxx_pc03.config"
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/fbhyve/prepare-pc03.sh	Tue Aug 27 21:47:57 2024 +0200
@@ -0,0 +1,20 @@
+#!/bin/sh
+
+#
+# Prepare disk devices/volumes
+#
+#   -> Intermediates
+zfs create -o canmount=off -o mountpoint=none zpool/bhyve
+zfs create -o canmount=off -o mountpoint=none -o primarycache=metadata -o secondarycache=none zpool/bhyve/test-local-bsdtools
+zfs create -o canmount=off zpool/bhyve/test-local-bsdtools/fbhyve
+
+
+#   -> Single disk setup
+zfs create -o volmode=dev -s -V 3G zpool/bhyve/test-local-bsdtools/fbhyve/disk0
+
+#
+# UEFI-Variables
+#
+#   -> Copy the template for the UEFI vars
+#
+cp -v /usr/local/share/uefi-firmware/BHYVE_UEFI_VARS.fd  /home/fag/work/ports/sysutils/local-bsdtools/tests/fbhyve/bootvars.fd
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/fbhyve/vm_xxx_pc03.config	Tue Aug 27 21:47:57 2024 +0200
@@ -0,0 +1,56 @@
+# FreeBSD 14.1
+# config.dump=true
+name=test-fbhyve
+# -A
+acpi_tables=true
+# -c ...
+cpus=2
+cores=2
+threads=1
+sockets=1
+# -m
+memory.size=1G
+# -S
+memory.wired=false
+# -D
+destroy_on_poweroff=true
+# -K de
+keyboard.layout=de
+# -u
+rtc.use_localtime=false
+# -H
+x86.vmexit_on_hlt=true
+# - P
+x86.vmexit_on_pause=true
+# -w
+x86.strictmsr=false
+# -s 0,amd_hostbridge
+pci.0.0.0.device=hostbridge
+pci.0.0.0.pcireg.vendor=0x1022
+pci.0.0.0.pcireg.device=0x7432
+# -s 3,virtio-blk,/dev/xxx,sectorsize=512/2048
+pci.0.3.0.device=virtio-blk
+pci.0.3.0.path=/dev/zvol/zpool/bhyve/test-local-bsdtools/fbhyve/disk0
+pci.0.3.0.sectorsize=512/2048
+# -s 7,ahci-hd,/my/path,ro
+pci.0.7.0.device=ahci
+pci.0.7.0.port.0.type=hd
+pci.0.7.0.port.0.path=/home/test2/Downloads/OpenBSD/7.5/install75.img
+pci.0.7.0.port.0.ro=true
+# -s 10,virtio-net,tap1
+#pci.0.10.0.device=virtio-net
+#pci.0.10.0.backend=tap1
+# -s 11,virtio-net,tap3 -- bridge0 -- to easily have SSH access for SFTP
+#pci.0.11.0.device=virtio-net
+#pci.0.11.0.backend=tap3
+# -s 20,virtio-rnd
+pci.0.20.0.device=virtio-rnd
+# -s 31,lpc
+pci.0.31.0.device=lpc
+# -l com1,stdio
+lpc.com1.path=stdio
+# -l bootrom,/usr/local/share/uefi-firmware/BHYVE_UEFI.fd,/home/fag/bhyve/openbsd/bootvars.data
+lpc.bootrom=/usr/local/share/uefi-firmware/BHYVE_UEFI.fd
+lpc.bootvars=/home/fag/work/ports/sysutils/local-bsdtools/tests/fbhyve/bootvars.fd
+# fwcfg
+lpc.fwcfg=qemu