Files
vm-bhyve/lib/vm-run
benoitc d940e51c49 add netgraph switch support
This change add simiular support to VALE for netgraph switches. Switches
must be configured manually. Devices will be added using the bhyve
support of netgraph.

Link Num of a peer in the bridge is found by iterrating all devices
already setup in. (Similiar hack is found in jail example).
2022-04-18 06:17:19 +02:00

1102 lines
34 KiB
Bash

#!/bin/sh
#-------------------------------------------------------------------------+
# Copyright (C) 2016 Matt Churchyard (churchers@gmail.com)
# All rights reserved
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted providing that the following conditions
# are met:
# 1. Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
# STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
# IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
# 'vm _run'
# run a virtual machine
# this is the background process that does all the work
# in most cases this should not be run directly
#
# @param string _name the name of the guest to run
# @param optional string _iso the iso file for an install
#
vm::run(){
local _name _iso _iso_dev
local _cpu _memory _bootdisk _bootdisk_dev _guest _wiredmem
local _guest_support _uefi _uuid _debug _hostbridge _loader
local _opts _devices _slot _install_slot _func=0 _taplist _exit _passdev
local _com _comports _comstring _logpath="/dev/null" _run=1
local _bhyve_options _action
cmd::parse_args "$@"
shift $?
_name="$1"
_iso="$2"
# try to load datstore details
datastore::get_guest "${_name}" || exit 5
# bail out immediately if guest running
vm::confirm_stopped "${_name}" "1" || exit 10
config::load "${VM_DS_PATH}/${_name}/${_name}.conf"
config::get "_memory" "memory"
config::get "_loader" "loader"
config::get "_bootdisk" "disk0_name"
config::get "_bootdisk_dev" "disk0_dev" "file"
config::get "_hostbridge" "hostbridge" "standard"
config::get "_comports" "comports" "com1"
config::get "_uuid" "uuid"
config::get "_debug" "debug" "no"
config::get "_bhyve_options" "bhyve_options"
config::get "_slot" "start_slot" "4"
config::get "_install_slot" "install_slot" "3"
# generate a uuid if we don't have one already
if [ -z "${_uuid}" ]; then
_uuid=$(uuidgen)
config::set "${_name}" "uuid" "${_uuid}"
fi
# get cpu topology
vm::__cpu "_cpu"
util::log_rotate "guest" "${_name}"
util::log "guest" "${_name}" \
"initialising" \
" [loader: ${_loader}]" \
" [cpu: ${_cpu}]" \
" [memory: ${_memory}]" \
" [hostbridge: ${_hostbridge}]" \
" [com ports: ${_comports}]" \
" [uuid: ${_uuid}]" \
" [debug mode: ${_debug}]" \
" [primary disk: ${_bootdisk}]" \
" [primary disk dev: ${_bootdisk_dev}]"
# check basic settings
if [ -z "${_loader}" -o -z "${_cpu}" -o -z "${_memory}" ]; then
util::log "guest" "${_name}" "fatal; unable to start - missing required configuration"
exit 15
fi
# check ug
if [ -n "${VM_NO_UG}" ]; then
# only FreeBSD guests. these start direct in 64bit mode and don't need UG
if [ "${_loader}" != "bhyveload" ]; then
util::log "guest" "${_name}" "fatal; unable to start - no unrestricted guest support"
exit 15
fi
# only 1 vcpu
if [ "${_cpu}" != "1" ]; then
_cpu=1
util::log "guest" "${_name}" "warning; no unrestricted guest support. reducing vcpu count to 1"
fi
fi
# default bhyve options
_opts="-AHP"
# ignore access to unimplemented Model Specific Registers?
config::yesno "ignore_msr" && _opts="${_opts}w"
# if uefi, make sure we have bootrom, then update options for uefi support
if [ "${_loader%-*}" = "uefi" ]; then
vm::uefi
fi
# add any custom bhyve options
[ -n "${_bhyve_options}" ] && _opts="${_opts} ${_bhyve_options}"
# if we have passthru, check vt-d or amdvi support now and exit
config::get "_passdev" "passthru0"
if [ -n "${_passdev}" ] && ! util::check_bhyve_iommu; then
util::log "guest" "${_name}" "fatal; pci passthrough not supported on this system (no VT-d or amdvi)"
exit 15
fi
# wired memory?
if config::yesno "wired_memory"; then
_wiredmem="1"
_opts="${_opts} -S"
fi
# set cpu/memory and uuid in opts
_opts="-c ${_cpu} -m ${_memory} ${_opts}"
[ -n "${_uuid}" ] && _opts="${_opts} -U ${_uuid}"
# set utc time in opts if requested
if config::yesno "utctime" yes; then
if [ ${VERSION_BSD} -ge 1002000 ]; then
_opts="${_opts} -u"
else
util::log "guest" "${_name}" "warning; utc time requested but not available pre FreeBSD 10.2"
fi
fi
# send bhyve output to bhyve.log if debug=yes
util::yesno "${_debug}" && _logpath="${VM_DS_PATH}/${_name}/bhyve.log"
# complete the boot disk path
[ -n "${_bootdisk}" ] && vm::get_disk_path "_bootdisk" "${_name}" "${_bootdisk}" "${_bootdisk_dev}"
# build bhyve device string
vm::bhyve_device_comports
vm::bhyve_device_basic
vm::bhyve_device_disks
vm::bhyve_device_networking
vm::bhyve_device_rand
vm::bhyve_device_passthru
vm::bhyve_device_fbuf
vm::bhyve_device_mouse
vm::bhyve_device_sound
vm::bhyve_device_console
vm::lock
util::log "guest" "${_name}" "booting"
cd /
while true; do
# destroy existing vmm
# freebsd seems happy to run a bhyveload/bhyve loop
# grub-bhyve doesn't seem to like it for a lot of users
# Peter says don't destroy in Windows instructions, so don't if in UEFI mode
if [ -e "/dev/vmm/${_name}" -a -z "${_uefi}" ]; then
bhyvectl --vm="${_name}" --destroy >/dev/null 2>&1
if [ $? -ne 0 ]; then
util::log "guest" "${_name}" "fatal; failed to destroy existing vmm device"
_exit=15
break
fi
sleep 1
fi
# run any prestart script while guest is fully down
vm::prestart
# add install iso or disk image
if [ -n "${_iso}" ]; then
if echo "${_iso}" | grep -iqs '.iso$'; then
_iso_dev="-s ${_install_slot}:0,ahci-cd,${_iso},ro"
else
_iso_dev="-s ${_install_slot}:0,ahci-hd,${_iso},ro"
fi
fi
# use null.iso if not an install and uefi firmware
# old instructions but some windows versions apparently needed this present
[ -z "${_iso}" -a "${_loader}" = "uefi" ] && config::yesno "nulliso_fix" && \
_iso_dev="-s ${_install_slot}:0,ahci-cd,${vm_dir}/.config/null.iso"
# reasonably ugly hack to remove wait option after first run
[ "${_run}" -eq "2" ] && vm::bhyve_device_fbuf_clear_wait
# load guest
if [ -z "${_uefi}" ]; then
guest::load "${_iso}"
_exit=$?
# check no errors
if [ ${_exit} -ne 0 ]; then
util::log "guest" "${_name}" "fatal; loader returned error ${_exit}"
break
fi
fi
util::log "guest" "${_name}" " [bhyve options: ${_opts}]" \
" [bhyve devices: ${_devices}]" \
" [bhyve console: ${_comstring}]"
[ -n "${_iso_dev}" ] && util::log "guest" "${_name}" " [bhyve iso device: ${_iso_dev}]"
util::log "guest" "${_name}" "starting bhyve (run ${_run})"
# call rctl now as next line will block until bhyve exits
rctl::set &
# actually run bhyve!
# we're already in the background so we just wait for it to exit
bhyve ${_opts} \
${_devices} \
${_iso_dev} \
${_comstring} \
${_name} 2> "${_logpath}"
# get bhyve exit code
_exit=$?
util::log "guest" "${_name}" "bhyve exited with status ${_exit}"
# remove any console sockets
rm ${VM_DS_PATH}/${_name}/vtcon.* >/dev/null 2>&1
# decide what to do with exit code
vm::handle_exit "_action" ${_exit}
[ "${_action}" != "restart" ] && break
util::log "guest" "${_name}" "restarting"
# remove install iso so guest reboots from disk
# after install non-uefi guests will still get install cd until a full shutdown+restart
# as we don't reset _iso_dev
_iso=""
_run=$((_run + 1))
done
# destroy taps
for _devices in ${_taplist}; do
util::log "guest" "${_name}" "destroying network device ${_devices}"
ifconfig "${_devices}" destroy
done
util::log "guest" "${_name}" "stopped"
[ -e "/dev/vmm/${_name}" ] && bhyvectl --destroy --vm=${_name} >/dev/null 2>&1
vm::unlock
exit ${_exit}
}
# creates options to use a uefi bootrom
#
# @modifies _opts _uefi
#
vm::uefi(){
local _bootrom
if [ ${VERSION_BSD} -lt 1002509 ]; then
util::log "guest" "${_name}" "fatal; uefi guests can only be run on FreeBSD 10.3 or newer"
exit 15
fi
case "${_loader}" in
uefi-devel)
_bootrom="/usr/local/share/uefi-firmware/BHYVE_UEFI_CODE-devel.fd"
;;
uefi-csm)
_bootrom="/usr/local/share/uefi-firmware/BHYVE_UEFI_CSM.fd"
;;
uefi-custom)
_bootrom="${VM_DS_PATH}/.config/BHYVE_UEFI.fd"
;;
*)
_bootrom="/usr/local/share/uefi-firmware/BHYVE_UEFI.fd"
;;
esac
if [ ! -e "${_bootrom}" ]; then
util::log "guest" "${_name}" "fatal; unable to locate firmware ${_bootrom}"
exit 15
fi
# should we store uefi vars?
if config::yesno "uefi_vars"; then
# do we already have a storage file for this guest?
if [ -e "${VM_DS_PATH}/${_name}/uefi-vars.fd" ]; then
:
elif [ -e "/usr/local/share/uefi-firmware/BHYVE_UEFI_VARS.fd" ]; then
# create a copy and use
cp "/usr/local/share/uefi-firmware/BHYVE_UEFI_VARS.fd" "${VM_DS_PATH}/${_name}/uefi-vars.fd"
else
util::log "guest" "${_name}" "fatal; unable to locate UEFI vars database or template"
exit 15
fi
_bootrom="${_bootrom},${VM_DS_PATH}/${_name}/uefi-vars.fd"
fi
_opts="-Hwl bootrom,${_bootrom}"
_uefi="yes"
}
# decide how to handle bhyve exit code
#
# @param string _var variable to put action into
# @param int _code bhyve exit code
#
vm::handle_exit(){
local _var="$1"
local _code="$2"
# check exit code
# get relevant action from config, defaulting to the behaviour
# we'd normally expect. we don't currently allow overriding shutdown
# as it makes it impossible to actually stop a guest cleanly
#
case "${_code}" in
0) config::get "${_var}" "on_restart" "restart" ;;
1) ;&
2) if [ -e "${VM_DS_PATH}/${_name}/restart" ]; then
setvar "${_var}" "restart"
unlink "${VM_DS_PATH}/${_name}/restart" >/dev/null 2>&1
else
setvar "${_var}" "shutdown"
fi
;;
*) config::get "${_var}" "on_fault" "shutdown" ;;
esac
}
# lock a vm
# stop another instance being started on this or another host
# we write hostname so vm-bhyve can inform user which host locked a vm
#
# @param string - the name of the guest to lock
#
vm::lock(){
hostname > "${VM_DS_PATH}/${_name}/run.lock"
}
# unlock a vm
#
# @param string - the name of the guest to unlock
#
vm::unlock(){
unlink "${VM_DS_PATH}/${_name}/run.lock" >/dev/null 2>&1
unlink "${VM_DS_PATH}/${_name}/console" >/dev/null 2>&1
}
# create string for guest com ports
# this builds the '-l comX' part of the bhyve command into _comstring
# _com is used by bhyveload|grub_bhyve so we set that to the first
# com port we come across.
# The nmdm devices are written to $vm_dir/{guest}/console so we can
# read them back later for the 'vm console' command
#
# @modifies _com _comstring
#
vm::bhyve_device_comports(){
local _port _num=1 _tmux_name
local _port_name
unlink "${VM_DS_PATH}/${_name}/console" >/dev/null 2>&1
for _port in ${_comports}; do
if [ ${_num} -eq 1 ]; then
# if tmux mode, log this to the console data
if [ -n "${VM_OPT_TMUX}" ]; then
_tmux_name=$(echo "${_name}" | tr "." "~")
echo "${_port}=tmux/${_tmux_name}" >> "${VM_DS_PATH}/${_name}/console"
fi
# if foreground mode, we don't configure a serial port
if [ -n "${VM_OPT_FOREGROUND}" ]; then
_comstring="-l ${_port},stdio"
_num=$((_num + 1))
continue
fi
fi
# generate a port name unique to this vm and port number
_port_name="/dev/nmdm-${_name}.${_num}"
# use first com port for the loader
[ ${_num} -eq 1 ] && _com="${_port_name}A"
echo "${_port}=${_port_name}B" >> "${VM_DS_PATH}/${_name}/console"
_comstring="${_comstring}${_comstring:+ }-l ${_port},${_port_name}A"
_num=$((_num + 1))
done
}
# get bhyve device string for basic devices
# hostbridge & lpc on their own slots
# windows requires slot 0 & 31, nothing else cares fortunately
#
# @modifies _devices
#
vm::bhyve_device_basic(){
# add hostbridge
case "$_hostbridge" in
no*) ;;
amd) _devices="-s 0,amd_hostbridge" ;;
*) _devices="-s 0,hostbridge" ;;
esac
# lpc
_devices="${_devices}${_devices:+ }-s 31,lpc"
}
# get bhyve device string for disk devices
# read all disks starting at 0 and add to the _devices string
# this is done first so disks will start at slot 4. For uefi,
# we move through slots and stop at slot 6. For non-uefi we
# step through all functions and just keep going
#
# since r302459 the ahci controller supports
# up to 32 devices per controller. by default we set
# the device limit to 1 and use the original syntax, but
# this can be overridden by setting the ahci_device_limit
# guest option to an integer between 2 and 32.
#
# @modifies _devices _slot
#
vm::bhyve_device_disks(){
local _disk _type _dev _path _opts _ahci _atype
local _ahci_num=0 _num=0 _add _ahci_multi
local _ahci_limit=1
# check if user has set a per-controller device limit
config::get "_ahci_multi" "ahci_device_limit"
if [ ${VERSION_BSD} -ge 1200000 ] || \
[ ${VERSION_BSD} -ge 1101000 ] || \
[ ${VERSION_BSD} -ge 1004000 ]; then
if [ -n "${_ahci_multi}" ]; then
# see if it's numeric
echo "${_ahci_multi}" | egrep -iqs '^[0-9]+$'
[ $? -eq 0 -a ${_ahci_multi} -gt 1 -a ${_ahci_multi} -le 32 ] && \
_ahci_limit="${_ahci_multi}"
fi
fi
# get all disks
while true; do
config::get "_disk" "disk${_num}_name"
config::get "_type" "disk${_num}_type"
[ -z "${_disk}" -o -z "${_type}" ] && break
config::get "_dev" "disk${_num}_dev"
config::get "_opts" "disk${_num}_opts"
if [ ${_func} -ge 8 ]; then
_func=0
_slot=$((_slot + 1))
fi
vm::get_disk_path "_path" "${_name}" "${_disk}" "${_dev}"
# ahci device and multi mode?
if [ ${_ahci_limit} -gt 1 -a "${_type%%-*}" = "ahci" ]; then
# check device type
case "${_type}" in
ahci-cd)
_atype="cd"
;&
ahci-hd)
[ -z "${_atype}" ] && _atype="hd"
_ahci="${_ahci},${_atype}:${_path}"
[ -n "${_opts}" ] && _ahci="${_ahci},${_opts}"
_ahci_num=$((_ahci_num + 1))
# we need to move to another controller if we get to the limit
if [ ${_ahci_num} -ge ${_ahci_limit} ]; then
_devices="${_devices} -s ${_slot}:${_func},ahci${_ahci}"
_ahci=""
_ahci_num=0
_add=1
fi
;;
esac
_atype=""
else
_devices="${_devices} -s ${_slot}:${_func},${_type},${_path}"
[ -n "${_opts}" ] && _devices="${_devices},${_opts}"
_add=1
fi
# have we just added a device?
if [ -n "${_add}" ]; then
if [ -n "${_uefi}" ]; then
_slot=$((_slot + 1))
else
_func=$((_func + 1))
fi
_add=""
fi
_num=$((_num + 1))
done
# have ahci devices left?
if [ -n "${_ahci}" ]; then
_devices="${_devices} -s ${_slot}:${_func},ahci${_ahci}"
[ -n "${_uefi}" ] && _slot=$((_slot + 1))
fi
# move to next slot if we have devices
# unless uefi as we already inc slot in uefi mode
if [ ${_num} -ge 1 -a -z "${_uefi}" ]; then
_slot=$((_slot + 1))
_func=0
fi
}
# get bhyve device string for networking
# we dynamically create a new tap device for each interface
# if we can find the correct bridge, we then add the tap as a member
# we add each tap to __taplist from vm::run which it will
# use to desstroy them all on shutdown
#
# @modifies _devices _slot _taplist
#
vm::bhyve_device_networking(){
local _type _switch _mac _custom_tap _tap _sid _mtu
local _num=0 _member_type _sw_type _iname
while true; do
config::get "_type" "network${_num}_type"
[ -z "${_type}" ] && break
config::get "_switch" "network${_num}_switch"
config::get "_mac" "network${_num}_mac"
config::get "_custom_tap" "network${_num}_device"
config::get "_iname" "network${_num}_name"
# set a static mac if we don't have one
[ -z "${_mac}" ] && vm::generate_static_mac
# move slot if we've hit function 8
if [ ${_func} -ge 8 ]; then
_func=0
_slot=$((_slot + 1))
fi
# get type of switch
switch::type "_sw_type" "${_switch}"
if [ "${_sw_type}" = "vale" ]; then
# create a vale port id
switch::vale::id "_tap" "${_switch}" "${_mac}"
util::log "guest" "${_name}" "adding vale interface ${_tap} (${_switch})"
_devices="${_devices} -s ${_slot}:${_func},${_type},${_tap}"
[ -n "${_mac}" ] && _devices="${_devices},mac=${_mac}"
_func=$((_func + 1))
elif [ "${_sw_type}" = "netgraph" ]; then
# create a netgraph peer
switch::netgraph::id "_path" "${_switch}"
util::log "guest" "${_name}" "adding netgraph interface ${_path} (${_switch})"
_devices="${_devices} -s ${_slot}:${_func},${_type},${_path}"
[ -n "${_mac}" ] && _devices="${_devices},mac=${_mac}"
_func=$((_func + 1))
else
# create interface
if [ -n "${_custom_tap}" ]; then
_tap="${_custom_tap}"
elif [ -n "${_iname}" ]; then
_tap=$(ifconfig tap create name "${_iname}")
else
_tap=$(ifconfig tap create)
fi
# should this be a span member?
_member_type="addm"
config::yesno "network${_num}_span" && _member_type="span"
if [ -n "${_tap}" ]; then
util::log "guest" "${_name}" "initialising network device ${_tap}"
ifconfig "${_tap}" descr "vmnet/${_name}/${_num}/${_switch:-custom}" group vm-port >/dev/null 2>&1
if [ -n "${_switch}" ]; then
switch::id "_sid" "${_switch}"
if [ -n "${_sid}" ]; then
_mtu=$(ifconfig "${_sid}" | head -n1 | awk '{print $NF}')
if [ "${_mtu}" != "1500" ]; then
util::log "guest" "${_name}" "setting mtu of ${_tap} to ${_mtu}"
ifconfig "${_tap}" mtu "${_mtu}" >/dev/null 2>&1
fi
util::log "guest" "${_name}" "adding ${_tap} -> ${_sid} (${_switch} ${_member_type})"
ifconfig "${_sid}" "${_member_type}" "${_tap}" >/dev/null 2>&1 || util::log "guest" "${_name}" "failed to add ${_tap} to ${_sid}"
util::log "guest" "${_name}" "bring up ${_tap} -> ${_sid} (${_switch} ${_member_type})"
ifconfig "${_tap}" up >/dev/null 2>&1 || util::log "guest" "${_name}" "failed to bring up ${_tap} in ${_sid}"
# set private if configured
switch::is_private "${_switch}" && ifconfig "${_sid}" "private" "${_tap}" >/dev/null 2>&1
else
util::log "guest" "${_name}" "failed to find virtual switch '${_switch}'"
fi
fi
_devices="${_devices} -s ${_slot}:${_func},${_type},${_tap}"
[ -n "${_mac}" ] && _devices="${_devices},mac=${_mac}"
_func=$((_func + 1))
[ -z "${_custom_tap}" ] && _taplist="${_taplist}${_taplist:+ }${_tap}"
fi
fi
_num=$((_num + 1))
done
if [ ${_num} -ge 1 ]; then
_slot=$((_slot + 1))
_func=0
fi
}
# check if user wants a virtio-rand device
#
# @modifies _devices _slot
#
vm::bhyve_device_rand(){
if config::yesno "virt_random"; then
_devices="${_devices} -s ${_slot}:0,virtio-rnd"
_slot=$((_slot + 1))
fi
}
# add frame buffer output
#
vm::bhyve_device_fbuf(){
local _port _listen _res _pass _vga _wait
local _fbuf_conf
# only works in uefi mode
[ -z "${_uefi}" ] && return 0
# check graphics enabled
! config::yesno "graphics" && return 0
# only available in 11+
if [ ${VERSION_BSD} -lt 1100000 ]; then
util::log "guest" "${_name}" "warning; UEFI graphics is only available in FreeBSD 11 and newer"
return 1
fi
config::get "_port" "graphics_port"
config::get "_listen" "graphics_listen"
config::get "_res" "graphics_res"
config::get "_vga" "graphics_vga"
config::get "_pass" "vnc_password"
config::get "_wait" "graphics_wait" "auto"
# check if graphics_wait is auto
# auto will count as yes so we need to unset it if we're not in an install.
[ "${_wait}" = "auto" -a -z "${_iso}" ] && _wait="no"
# try to get port
# return if we can't
if [ -z "${_port}" ]; then
vm::find_available_net_port "_port" "5900"
if [ -z "${_port}" ]; then
util::log "guest" "${_name}" "warning; unable to allocate a network port for graphics/vnc"
return 1
fi
util::log "guest" "${_name}" "dynamically allocated port ${_port} for vnc connections"
fi
# add ip, port, resolution, wait
_fbuf_conf="tcp=${_listen:-0.0.0.0}:${_port}"
[ -n "${_res}" ] && _fbuf_conf="${_fbuf_conf},w=${_res%%x*},h=${_res##*x}"
[ -n "${_vga}" ] && _fbuf_conf="${_fbuf_conf},vga=${_vga}"
[ -n "${_pass}" ] && _fbuf_conf="${_fbuf_conf},password=${_pass}"
util::yesno "${_wait}" && _fbuf_conf="${_fbuf_conf},wait"
# write vnc port to console data
echo "vnc=${_listen:-0.0.0.0}:${_port}" >> "${VM_DS_PATH}/${_name}/console"
# add device
_devices="${_devices} -s ${_slot}:0,fbuf,${_fbuf_conf}"
_slot=$((_slot + 1))
}
# remove wait option if we're in auto mode
# fairly ugly code that uses sed to look for ",wait{word boundary}"
# and removes it. Hopefully this will never match anything else in
# the device string...
#
# @modifies _devices
#
vm::bhyve_device_fbuf_clear_wait(){
local _wait
[ -z "${_uefi}" ] && return
config::get "_wait" "graphics_wait" "auto"
[ "${_wait}" = "auto" ] && _devices=$(echo "${_devices}" | sed 's@,wait[[:>:]]@@')
}
# add a xhci mouse device
#
vm::bhyve_device_mouse(){
[ ${VERSION_BSD} -lt 1100000 ] && return 0
# add a tablet device if enabled
if config::yesno "xhci_mouse"; then
_devices="${_devices} -s ${_slot}:0,xhci,tablet"
_slot=$((_slot + 1))
fi
}
vm::bhyve_device_sound(){
local _play
config::get "_play" "sound_play" "/dev/dsp0"
if config::yesno "sound"; then
_devices="${_devices} -s ${_slot}:0,hda,play=${_play}"
_slot=$((_slot + 1))
fi
}
# add virtio_console devices to the guest
#
# @modifies _devices _slot
#
vm::bhyve_device_console(){
local _console _curr=0
local _dev_str
[ ${VERSION_BSD} -lt 1102000 ] && return 0
config::get "_console" "virt_console0"
[ -z "${_console}" ] && return 0
# add ports
while [ -n "${_console}" -a ${_curr} -lt 16 ]; do
# if set to "yes/on/1", just use the console number as port name
case "${_console}" in
[Yy][Ee][Ss]|[Oo][Nn]|1) _console="${_curr}" ;;
esac
_dev_str="${_dev_str},${_console}=${VM_DS_PATH}/${_name}/vtcon.${_console}"
_curr=$((_curr + 1))
config::get "_console" "virt_console${_curr}"
done
_devices="${_devices} -s ${_slot}:0,virtio-console${_dev_str}"
_slot=$((_slot + 1))
}
# get any pci passthrough devices
# FreeBSD 11 needs wired memory so update _opts in that case if
# we have any pass through devices
#
# @modifies _devices _slot _opts _wiredmem
#
vm::bhyve_device_passthru(){
local _dev _orig_slot _func=0
local _last_orig_slot
local _num=0
while true; do
config::get "_dev" "passthru${_num}"
[ -z "${_dev}" ] && break
# see if there's an = sign
# we allow A/B/C=D:E to force D:E as the guest SLOT:FUNC
if echo "${_dev}" | grep -qs "="; then
_devices="${_devices} -s ${_dev##*=},passthru,${_dev%%=*}"
else
_orig_slot=${_dev%%/*}
# only move to new slot if the original device is on a different slot to the last one.
# if user wants to passthru a device that has multiple functions which must stay together
# on one slot, they should be together in configuration file
if [ -n "${_last_orig_slot}" -a "${_last_orig_slot}" != "${_orig_slot}" ]; then
_slot=$((_slot + 1))
_func=0
fi
_devices="${_devices} -s ${_slot}:${_func},passthru,${_dev}"
_last_orig_slot=${_orig_slot}
_func=$((_func + 1))
fi
_num=$((_num + 1))
done
if [ ${_num} -ge 1 ]; then
_slot=$((_slot + 1))
# add wired memory for 10.3+
[ ${VERSION_BSD} -ge 1003000 ] && _opts="${_opts} -S" && _wiredmem="1"
fi
}
# get the path to a disk image depending on type.
# the disk name in configuration is usually the name of the file
# or zvol, directly under the guest directory/dataset.
# if a user wants the disk image anywhere else, they can specify
# the following
# diskX_dev="custom"
# diskX_name="/full/path/to/disk/device/or/file"
#
# @param string _var variable to put disk path into
# @param string _name the name of the guest
# @param string _disk the name of the disk
# @param string _disk_dev=file|zvol|sparse-zvol|custom type of device
#
vm::get_disk_path(){
local _var="$1"
local _name="$2"
local _disk="$3"
local _disk_dev="$4"
case "${_disk_dev}" in
zvol) ;&
sparse-zvol) setvar "${_var}" "/dev/zvol/${VM_DS_ZFS_DATASET}/${_name}/${_disk}" ;;
custom) setvar "${_var}" "${_disk}" ;;
iscsi) info::__find_iscsi "${_var}" "${_disk}" ;;
*) setvar "${_var}" "${VM_DS_PATH}/${_name}/${_disk}" ;;
esac
}
# looks for a prestart setting and runs it if possible
# this will execute before each start/reboot
#
# script is given the guest name and full path as arguments
#
vm::prestart(){
local _script
config::get "_script" "prestart" || return 0
echo "${_script}" | grep -qs '^/'
[ $? -ne 0 ] && _script="${VM_DS_PATH}/${_name}/${_script}"
[ ! -x "${_script}" ] && return 1
util::log "guest" "${_name}" "running prestart script ${_script} ${_name} ${VM_DS_ZFS_DATASET}/${_name}"
(
cd "${VM_DS_PATH}/${_name}"
exec "${_script}" "${_name}" "${VM_DS_ZFS_DATASET}/${_name}"
)
}
# get a list of all running virtual machines
# this loads a list of all running guests into global variables
# both variables are in the following format with one guest per line
# "pid guest"
#
# VM_RUN_BHYVE = all running bhyve instances
# VM_RUN_LOAD = all loading guests (bhyveload|grub-bhyve)
#
# @modifies VM_RUN_BHYVE VM_RUN_LOAD
#
vm::running_load(){
VM_RUN_BHYVE=$(pgrep -fl "bhyve:" | awk '{print $1,$NF}')
VM_RUN_LOAD=$(pgrep -fl "grub-bhyve|bhyveload" | awk '{print $1,$NF}')
}
# see if a specific virtual machine is running
# we search the VM_RUN_BHYVE and VM_RUN_LOAD variables looking for the
# specified guest. If found we output "Running" or "Bootloader", along with
# the PID. If not running we output "Stopped"
#
# @param string _var variable to put result into
# @param string _name name of the guest to look for
# @return success if running
#
vm::running_check(){
local _var="$1"
local _var2="$2"
local _name="$3"
local IFS=$'\n'
local _entry
for _entry in ${VM_RUN_BHYVE}; do
if [ "${_entry##* }" = "${_name}" ]; then
setvar "${_var}" "Running (${_entry%% *})"
setvar "${_var2}" "${_entry%% *}"
return 0
fi
done
for _entry in ${VM_RUN_LOAD}; do
if [ "${_entry##* }" = "${_name}" ]; then
setvar "${_var}" "Bootloader (${_entry%% *})"
setvar "${_var2}" "${_entry%% *}"
return 0
fi
done
setvar "${_var}" "Stopped"
return 1
}
# make sure a virtual machine is not running
# display warning if it is. up to caller to decide whether to continue.
# on unclean shutdown, lock files may not be cleared up by vm-bhyve,
# we now provide option to skip the lock file if it references this host,
# and there is no /dev/vmm/{_name}.
#
# @param string _name the guest name
# @param int _skip_lock skip local lock file if no /dev/vmm found
# @return success if not running
#
vm::confirm_stopped(){
local _name="$1"
local _skip_lock="$2"
local _host _our_host
_our_host=$(hostname)
# check vm-bhyve lock
# this will err even if guest is running on another node
if [ -e "${VM_DS_PATH}/${_name}/run.lock" ]; then
_host=$(head -n 1 "${VM_DS_PATH}/${_name}/run.lock")
if [ "${_host}" != "${_our_host}" -o "${_skip_lock}" != "1" ]; then
util::warn "${_name} appears to be running on ${_host} (locked)"
return 2
fi
fi
# check local machine, just in case guest run manually
if [ -e "/dev/vmm/${_name}" ]; then
util::warn "${_name} appears to be running locally (vmm exists)"
return 1
fi
return 0
}
# generate a static mac address for a guest.
# we now make sure all interfaces have a static mac so
# there's no worry of guest problems due to it changing.
# bhyve is allocated 58:9c:fc:0x:xx:xx, so we try and
# randomise the last 20 bits (5 hex digits).
# using guest name, interface number and time as seed,
# with an incrementing integer just in case we happen to
# get 5 zeros.
#
vm::generate_static_mac(){
local _time=$(date +%s)
local _key _part="0:00:00"
local _base _int=0
_base="${_name}:${_num}:${_time}"
# generate the last 5 digits
# make sure we don't get 0:00:00 (see sys/net/ieee_oui.h)
while [ "${_part}" = "0:00:00" ]; do
_key="${_base}:${_int}"
_part=$(md5 -qs "${_key}" | awk 'BEGIN {FS="";OFS=":"}; {print $1,$2$3,$4$5}')
_int=$((_int + 1))
done
# add base bhyve OUI
_mac="58:9c:fc:0${_part}"
util::log "guest" "${_name}" "generated static mac ${_mac} (based on '${_key}')"
config::set "${_name}" "network${_num}_mac" "${_mac}"
}
# try to find an available network port to listen on
# this isn't clever enough to diffeential ports on unique ip's yet
#
# @param string _var variable to put port into
# @param int _curr the port to start searching from
# @return true if we found a port
#
vm::find_available_net_port(){
local _var="$1"
local _curr="$2"
local _open _line _max _used
local IFS=$'\n'
# stop searching after 200 ports
_max=$((_curr + 200))
# get list of open ports
_open=$(netstat -an | grep LISTEN | awk '{print $4}' | awk -F. '{print $NF}')
while [ "${_curr}" -lt "${_max}" ]; do
# reset used flag
_used=""
# see if current port is used
for _line in ${_open}; do
[ "${_line}" = "${_curr}" ] && _used="1"
done
# this port available?
if [ -z "${_used}" ]; then
setvar "${_var}" "${_curr}"
return 0
fi
_curr=$((_curr + 1))
done
# not found a port
setvar "${_var}" ""
return 1
}
# remove any values from configuration that should be unique
# such as uuid and mac addresses. these should be generated
# automatically at runtime
#
# used for cloning or imaging existing guests
# should this remove devices like passthru that cannot be shared?
#
# @param string _name name of guest to generalise
#
vm::generalise(){
local _name="$1"
local _entry _num=0
config::load "${VM_DS_PATH}/${_name}/${_name}.conf"
config::remove "${_name}" "uuid"
while true; do
config::get "_entry" "network${_num}_mac"
[ -z "${_entry}" ] && break
config::remove "${_name}" "network${_num}_mac"
_num=$((_num + 1))
done
}
# get number of cpus from the configuration file, and also
# look for sockets/cores/threads options.
# returns the bhyve cpu config string
#
# @param string _var variable to put cpu configuration into
#
vm::__cpu(){
local _var="$1"
local _c_cpu _c_socket _c_core _c_thread
local _config
config::get "_c_cpu" "cpu"
[ -n "${_c_cpu}" ] || util::err "fatal; unable to determine cpu count"
_config="${_c_cpu}"
config::get "_c_socket" "cpu_sockets"
config::get "_c_core" "cpu_cores"
config::get "_c_thread" "cpu_threads"
[ -n "${_c_socket}" ] && _config="${_config},sockets=${_c_socket}"
[ -n "${_c_core}" ] && _config="${_config},cores=${_c_core}"
[ -n "${_c_thread}" ] && _config="${_config},threads=${_c_thread}"
setvar "${_var}" "${_config}"
}