505 lines
15 KiB
Bash
505 lines
15 KiB
Bash
#!/bin/sh
|
|
#-------------------------------------------------------------------------+
|
|
# Copyright (C) 2016 Matt Churchyard (churchers@gmail.com)
|
|
# Copyright (C) 2023 LAYLO
|
|
# 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}"
|
|
}
|