Files
vm-bhyve/lib/vm-info
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

504 lines
14 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 info'
# display a wealth of information about all guests, or one specified
#
# @param optional string[multiple] _name name of the guest to display
#
info::guest(){
local _name="$1"
local _bridge_list=$(ifconfig -g vm-switch)
local _ds
vm::running_load
# see if guest name(s) provided
if [ -n "${_name}" ]; then
while [ -n "${_name}" ]; do
datastore::get_guest "${_name}" || util::err "unable to locate virtual machine '${_name}'"
info::guest_show "${_name}"
shift
_name="$1"
done
exit
fi
# show all guests from all datastores
for _ds in ${VM_DATASTORE_LIST}; do
datastore::get "${_ds}" || continue
ls -1 "${VM_DS_PATH}" 2>/dev/null | \
while read _name; do
[ -e "${VM_DS_PATH}/${_name}/${_name}.conf" ] && info::guest_show "${_name}"
done
done
}
# 'vm switch info'
# display config of each virtual switch as well as stats and connected guests
#
# @param optional string[multiple] _switch name of switch to display
#
info::switch(){
local _switch="$1"
local _list
# load config file manually using non-core function
# this means we can share the config_output function with guest
config::load "${vm_dir}/.config/system.conf"
config::get "_list" "switch_list"
if [ -n "${_switch}" ]; then
while [ -n "${_switch}" ]; do
info::switch_show "${_switch}"
shift
_switch="$1"
done
exit
fi
for _switch in ${_list}; do
info::switch_show "${_switch}"
done
}
# display info for one virtual switch
#
# @param string _switch the name of the switch
#
info::switch_show(){
local _switch="$1"
local _type _bridge _vale _netgraph, _id
local _INDENT=" "
[ -z "${_switch}" ] && return 1
config::get "_type" "type_${_switch}" "standard"
config::get "_bridge" "bridge_${_switch}"
config::get "_vale" "vale_${_switch}"
config::get "_netgraph" "netgraph_${_switch}"
echo "------------------------"
echo "Virtual Switch: ${_switch}"
echo "------------------------"
echo "${_INDENT}type: ${_type}"
switch::id "_id" "${_switch}"
# we don't have a bridge for vale and netgraph switches
[ "${_type}" != "vale" ] && [ "${_type}" != "netgraph" ] && _bridge="${_id}"
echo "${_INDENT}ident: ${_id:--}"
info::__output_config "vlan_${_switch}" "vlan"
info::__output_config "ports_${_switch}" "physical-ports"
if [ -n "${_bridge}" ]; then
_stats=$(netstat -biI "${_bridge}" |grep '<Link#' | tail -n1 | awk '{ for (i=NF; i>1; i--) printf("%s ",$i); print $1; }' | awk '{print $5,$2}')
if [ -n "${_stats}" ]; then
_b_in=$(info::__bytes_human "${_stats%% *}")
_b_out=$(info::__bytes_human "${_stats##* }")
echo "${_INDENT}bytes-in: ${_stats%% *} (${_b_in})"
echo "${_INDENT}bytes-out: ${_stats##* } (${_b_out})"
fi
# show guest ports
info::switch_ports
fi
echo ""
}
# get all guest ports for current bridge
#
info::switch_ports(){
local _port_list=$(ifconfig "${_bridge}" |grep 'member: tap' |awk '{print $2}')
local _port _guest
for _port in ${_port_list}; do
_guest=$(ifconfig "${_port}" |grep 'description: vmnet' |awk '{print $2}' |cut -d'/' -f2)
echo ""
echo "${_INDENT}virtual-port"
echo "${_INDENT}${_INDENT}device: ${_port}"
echo "${_INDENT}${_INDENT}vm: ${_guest:--}"
done
}
# display the info for one guest
#
# @param string _name name of the guest to display
#
info::guest_show(){
local _name="$1"
local _conf="${VM_DS_PATH}/${_name}/${_name}.conf"
local _INDENT=" "
local _RUN="0"
local _res_mem _b_res_mem _global_run _port _opt _pid
[ -z "${_name}" ] && return 1
[ ! -f "${_conf}" ] && return 1
config::load "${_conf}"
# check local and global runstate
[ -e "/dev/vmm/${_name}" ] && _RUN="1"
vm::running_check "_global_run" "_pid" "${_name}"
_global_run=$(echo "${_global_run}" | tr '[:upper:]' '[:lower:]')
echo "------------------------"
echo "Virtual Machine: ${_name}"
echo "------------------------"
echo "${_INDENT}state: ${_global_run}"
echo "${_INDENT}datastore: ${VM_DS_NAME}"
# basic guest configuration
info::__output_config "loader" "" "none"
info::__output_config "uuid" "" "auto"
info::__output_config "cpu"
# check for a cpu topology
config::get "_opt" "cpu_sockets"
if [ -n "${_opt}" ]; then
echo -n "${_INDENT}cpu-topology: sockets=${_opt}"
config::get "_opt" "cpu_cores"
[ -n "${_opt}" ] && echo -n ", cores=${_opt}"
config::get "_opt" "cpu_threads"
[ -n "${_opt}" ] && echo -n ", threads=${_opt}"
echo ""
fi
info::__output_config "memory"
# running system details
if [ "${_RUN}" = "1" ]; then
_res_mem=$(bhyvectl --get-stats --vm="${_name}" |grep 'Resident memory' |awk '{print $3}')
if [ -n "${_res_mem}" ]; then
_b_res_mem=$(info::__bytes_human "${_res_mem}")
echo "${_INDENT}memory-resident: ${_res_mem} (${_b_res_mem})"
fi
# show com ports
if [ -e "${VM_DS_PATH}/${_name}/console" ]; then
echo ""
echo "${_INDENT}console-ports"
cat "${VM_DS_PATH}/${_name}/console" | \
while read _port; do
echo "${_INDENT}${_INDENT}${_port%%=*}: ${_port##*=}"
done
# virtio-console devices
info::guest_vtcon
fi
fi
# network interfaces
info::guest_networking
# disks
info::guest_disks
# zfs data
info::guest_zfs
echo ""
}
# display any virtio consoles
#
info::guest_vtcon(){
local _INDENT=" "
local _console _num=0
config::get "_console" "virt_console0"
[ -z "${_console}" ] && return 0
echo ""
while [ -n "${_console}" -a ${_num} -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="${_num}" ;;
esac
echo "${_INDENT}vtcon${_num}: ${VM_DS_PATH}/${_name}/vtcon.${_console}"
_num=$((_num + 1))
config::get "_console" "virt_console${_num}"
done
}
# display zfs snapshot/origin data
#
info::guest_zfs(){
local _INDENT=" "
local _data
[ -z "${VM_DS_ZFS}" ] && return 1
_data=$(zfs list -o name,used,creation -s creation -rHt snapshot "${VM_DS_ZFS_DATASET}/${_name}" | sed "s/^/${_INDENT}/")
if [ -n "${_data}" ]; then
echo ""
echo " snapshots"
echo "${_data}"
fi
_data=$(zfs get -Ho value origin "${VM_DS_ZFS_DATASET}/${_name}")
[ "${_data}" = "-" ] && return 0
echo ""
echo " clone-origin"
echo " ${_data}"
}
# display disks
#
info::guest_disks(){
local _num=0
local _disk _type _dev _path _size _b_size _used _b_used
local _INDENT=" "
while true; do
config::get "_disk" "disk${_num}_name"
config::get "_type" "disk${_num}_type"
config::get "_dev" "disk${_num}_dev" "file"
[ -z "${_disk}" -o -z "${_type}" ] && break
vm::get_disk_path "_path" "${_name}" "${_disk}" "${_dev}"
echo ""
echo " virtual-disk"
echo "${_INDENT}number: ${_num}"
info::__output_config "disk${_num}_dev" "device-type" "file"
info::__output_config "disk${_num}_type" "emulation"
info::__output_config "disk${_num}_opts" "options"
echo "${_INDENT}system-path: ${_path:--}"
_size=""
_used=""
if [ -n "${_path}" ]; then
case "${_dev}" in
file)
_size=$(stat -f%z "${_path}")
_used=$(du "${_path}" | awk '{print $1}')
_used=$((_used * 1024))
;;
zvol|sparse-zvol)
_size=$(zfs get -Hp volsize "${_path#/dev/zvol/}" |cut -f3)
_used=$(zfs get -Hp refer "${_path#/dev/zvol/}" |cut -f3)
;;
iscsi)
_size=$(sysctl -b kern.geom.conftxt | awk "/ ${_path#/dev/} /{print \$4}")
_used=${_size}
esac
if [ -n "${_size}" -a -n "${_used}" ]; then
_b_size=$(info::__bytes_human "${_size}")
_b_used=$(info::__bytes_human "${_used}")
echo "${_INDENT}bytes-size: ${_size} (${_b_size})"
echo "${_INDENT}bytes-used: ${_used} (${_b_used})"
fi
fi
_num=$((_num + 1))
done
}
# display networking configuration
#
info::guest_networking(){
local _num=0
local _int _id _tag _switch _stats _b_in _b_out
local _INDENT=" "
while true; do
config::get "_int" "network${_num}_type"
[ -z "${_int}" ] && break
echo ""
echo " network-interface"
echo "${_INDENT}number: ${_num}"
# basic interface config
info::__output_config "network${_num}_type" "emulation"
info::__output_config "network${_num}_switch" "virtual-switch"
info::__output_config "network${_num}_mac" "fixed-mac-address"
info::__output_config "network${_num}_device" "fixed-device"
# if running, try to get some more interface details
if [ "${_RUN}" = "1" ]; then
config::get "_switch" "network${_num}_switch"
_int=$(ifconfig | grep -B1 "vmnet-${_name}-${_num}-" | head -n1 | cut -d' ' -f1,6)
_id=${_int%%:*}
_tag=$(ifconfig | grep "vmnet-${_name}-${_num}-" | cut -d' ' -f2)
info::__find_bridge "_bridge" "${_id}"
echo "${_INDENT}active-device: ${_id:--}"
echo "${_INDENT}desc: ${_tag:--}"
echo "${_INDENT}mtu: ${_int##* }"
echo "${_INDENT}bridge: ${_bridge:--}"
if [ -n "${_id}" ]; then
_stats=$(netstat -biI "${_id}" |grep '<Link#' |tail -n1 |awk '{ for (i=NF; i>1; i--) printf("%s ",$i); print $1; }' |awk '{print $2,$5}')
_b_in=$(info::__bytes_human "${_stats%% *}")
_b_out=$(info::__bytes_human "${_stats##* }")
echo "${_INDENT}bytes-in: ${_stats%% *} (${_b_in})"
echo "${_INDENT}bytes-out: ${_stats##* } (${_b_out})"
fi
fi
_num=$((_num + 1))
done
}
# output a single configuration variable
# alwasy called once guest configuration has been loaded
#
# @param string _option config option to display
# @param optional string _title title to display instead of using option name
# @param optional string _default default value to display if not -
#
info::__output_config(){
local _option="$1"
local _title="$2"
local _default="$3"
local _var
config::get "_var" "${_option}" "${_default:--}"
[ -z "${_title}" ] && _title="${_option}"
echo "${_INDENT}${_title}: ${_var}"
}
# try and find the bridge an interface is a member of.
# we do this rather than just use switch::id as
# this should be able to locate the bridge even for devices
# that have been bridged manually and have no switch name configured
#
# @param string _var variable to put value into
# @param string _interface interface to look for
#
info::__find_bridge(){
local _var="$1"
local _interface="$2"
local _br _found
for _br in ${_bridge_list}; do
_found=$(ifconfig "${_br}" |grep member: |awk '{print $2}' |tr "\n" "," | grep "${_interface},")
if [ -n "${_found}" ]; then
setvar "${_var}" "${_br}"
return 0
fi
done
setvar "${_var}" ""
return 1
}
# format bytes to human readable
# convert to k,m,g or t
# output rounded number
#
# @param int _val the value to convert
#
info::__bytes_human(){
local _val="$1" _int _ext
local _dec="$2"
local _num="$3"
: ${_dec:=3}
: ${_val:=0}
: ${_num:=1}
_int=${_val%%.*}
while [ ${_int} -ge 1024 -a ${_num} -lt 5 ]; do
_val=$(echo "scale=3; ${_val}/1024" | bc)
_int=${_val%%.*}
_num=$((_num + 1))
done
case "${_num}" in
1) _ext="B" ;;
2) _ext="K" ;;
3) _ext="M" ;;
4) _ext="G" ;;
5) _ext="T" ;;
esac
export LC_ALL="C"
printf "%.${_dec}f%s" "${_val}" "${_ext}"
}
info::__find_iscsi() {
local _var="$1"
# _target format: [iqn.*]unique_name[/N] where N is the lun# and defaults
# to 0. The address before the unique_name can be omitted so long as
# the unique_name is sufficient to select only one output of iscsictl -L.
local _target="$2"
local _lun _col _found
# If no lun is specified, assume /0
_lun=${_target##*/}
[ "${_lun}" = "${_target}" ] && _lun=0
_target=${_target%/*}
_found=$(iscsictl -L -w 10 | grep ${_target} | wc -l)
if [ "${_found}" -ne 1 ]; then
setvar "${_var}" ""
util::err "Unable to locate unique iSCSI device ${_target}"
fi
# _col to be the column of iscsictl -L we want
_col=$((_lun + 4))
_found=$(iscsictl -L | awk "/${_target}/{print \$${_col}}")
if echo "${_found}" | egrep -q '^da[0-9]+$'; then
setvar "${_var}" /dev/"${_found}"
return 0
fi
util::err "Unable to locate iSCSI device ${_target}/${_lun}"
}