training-labs/labs/osbash/lib/osbash/kvm-functions.sh

455 lines
12 KiB
Bash

source "$CONFIG_DIR/provider.$PROVIDER"
#-------------------------------------------------------------------------------
# virt-install / virsh
#-------------------------------------------------------------------------------
VIRSH=virsh
VIRT_INSTALL=virt_install
: ${VIRT_LOG:=$LOG_DIR/virt.log}
function virsh {
mkdir -p "$(dirname "$VIRT_LOG")"
echo "$VIRSH_CALL" "$@" >> "$VIRT_LOG"
local rc=0
$VIRSH_CALL "$@" || rc=$?
if [ $rc -ne 0 ]; then
echo -e >&2 "${CError:-}FAILURE ($rc): virsh: ${*}${CReset:-}"
echo "FAILURE ($rc): $VIRSH_CALL $@" >> "$VIRT_LOG"
return 1
fi
}
function virt_install {
mkdir -p "$(dirname "$VIRT_LOG")"
echo "$VIRT_INSTALL_CALL" "$@" >> "$VIRT_LOG"
local rc=0
$VIRT_INSTALL_CALL "$@" || rc=$?
if [ $rc -ne 0 ]; then
echo -e >&2 "${CError:-}FAILURE ($rc): $VIRT_INSTALL_CALL ${*}${CReset:-}"
echo "FAILURE ($rc): $VIRT_INSTALL_CALL $@" >> "$VIRT_LOG"
return 1
fi
}
function virsh_uses_kvm {
$VIRSH capabilities | grep -q kvm
}
#-------------------------------------------------------------------------------
# VM status
#-------------------------------------------------------------------------------
function set_vm_group {
local vm_name=$1
$VIRSH desc "$vm_name" --config --live --title \
--new-desc "$vm_name: $VM_GROUP"
$VIRSH desc "$vm_name" --config --live --new-desc "All VMs with" \
"'$VM_GROUP' in their description title get shut down when a new" \
"cluster build starts."
}
function get_vm_group {
local vm_name=$1
$VIRSH desc "$vm_name" --title
}
function vm_exists {
local vm_name=$1
return $($VIRSH domstate "$vm_name" >/dev/null 2>&1)
}
function vm_is_running {
local vm_name=$1
return $($VIRSH domstate "$vm_name" 2>/dev/null | grep -q running)
}
function vm_wait_for_shutdown {
local vm_name=$1
echo -e >&2 -n "${CStatus:-}Machine shutting down${CReset:-}"
while $VIRSH domstate "$vm_name" | grep -q -e running -e "in shutdown"; do
echo -n .
sleep 1
done
echo >&2 -e "${CStatus:-}\nMachine powered off.${CReset:-}"
}
function vm_power_off {
local vm_name=$1
if vm_is_running "$vm_name"; then
echo -e >&2 "${CStatus:-}Powering off VM ${CData:-}\"$vm_name\"${CReset:-}"
$VIRSH destroy "$vm_name"
fi
}
function vm_acpi_shutdown {
local vm_name=$1
if vm_is_running "$vm_name"; then
echo -e >&2 "${CStatus:-}ACPI shutdown for VM ${CData:-}\"$vm_name\"${CReset:-}"
$VIRSH shutdown "$vm_name"
fi
}
function stop_running_cluster_vms {
local vm_id
$VIRSH list --uuid | while read vm_id; do
if [ -z "$vm_id" ]; then
continue
elif [[ "$(get_vm_group "$vm_id")" =~ $VM_GROUP ]]; then
# vm_id instead of vm_name works just as well
vm_acpi_shutdown "$vm_id"
vm_wait_for_shutdown "$vm_id"
fi
done
}
#-------------------------------------------------------------------------------
# Snapshots
#-------------------------------------------------------------------------------
function vm_snapshot_list_tree {
local vm_name=$1
$VIRSH snapshot-list --tree "$vm_name"
}
function vm_snapshot_list {
local vm_name=$1
$VIRSH snapshot-list "$vm_name"
}
function vm_snapshot_exists {
local vm_name=$1
local shot_name=$2
vm_snapshot_list "$vm_name" | grep -q "^ $shot_name "
}
function vm_snapshot {
local vm_name=$1
local shot_name=$2
$VIRSH snapshot-create-as "$vm_name" "$shot_name" "$vm_name: $shot_name"
}
function vm_snapshot_restore {
local vm_name=$1
local shot_name=$2
$VIRSH snapshot-revert "$vm_name" "$shot_name"
}
function vm_snapshot_restore_current {
local vm_name=$1
$VIRSH snapshot-revert "$vm_name" --current
}
#-------------------------------------------------------------------------------
# Network functions
#-------------------------------------------------------------------------------
# Get the MAC address from a node name (default network)
function node_to_mac {
local node=$1
local rc=""
local mac=""
echo >&2 "Waiting for MAC address."
while : ; do
mac=$($VIRSH dumpxml "$node"|grep -Po '[a-z0-9:]{17}'|head -n1) || rc=$?
if [ -n "$mac" ]; then
echo "$mac"
echo >&2
break
fi
sleep 1
echo >&2 -n .
done
}
# Get the IP address from a MAC address (default network)
function mac_to_ip {
local mac=$1
local rc=""
local ip=""
echo >&2 "Waiting for IP address."
while : ; do
ip=$(sudo arp -n|grep "$mac"|awk '{print $1}') || rc=$?
if [ -n "$ip" ]; then
echo >&2
echo "$ip"
break
fi
sleep 1
echo >&2 -n .
done
}
NODE_IP_DB=$LOG_DIR/node_ip.db
function node_to_ip {
local node=$1
local mac=$(node_to_mac "$node")
echo -e >&2 "${CInfo:-}MAC address for $node: ${CData:-}$mac${CReset:-}"
local ip=$(mac_to_ip "$mac")
echo -e >&2 "${CInfo:-}IP address for $node: ${CData:-}$ip${CReset:-}"
# Store node name, IP address, and MAC address in text file for later use
echo "$mac $ip $node" >> "$NODE_IP_DB"
# Return IP address to caller
echo "$ip"
}
# Get ssh IP address and port from node name (non-default networks)
function ssh_env_for_node {
local node=$1
# No port forwarding with KVM; ignore VM_SSH_PORT from config.<node>
VM_SSH_PORT=22
if [ -f "$NODE_IP_DB" ]; then
if grep "$node" "$NODE_IP_DB"; then
SSH_IP=$(grep " $node$" "$NODE_IP_DB"|awk '{print $2}')
return 0
else
echo >&2 "Node $node not found in $NODE_IP_DB."
fi
else
echo >&2 "$NODE_IP_DB missing."
fi
echo >&2 "Getting IP address through arp."
SSH_IP=$(node_to_ip "$node")
}
function virsh_define_network {
local net=labs-$1
local if_ip=$2
echo >&2 "Defining network $net ($if_ip)."
if ! $VIRSH net-info "$net" >/dev/null 2>&1; then
local cfg=$LOG_DIR/kvm-net-$net.xml
# FIXME Limit port forwarding to networks that need it.
cat << NETCFG > "$cfg"
<network>
<name>$net</name>
<forward mode='nat'/>
<ip address='$if_ip' netmask='255.255.255.0'>
</ip>
</network>
NETCFG
$VIRSH net-define "$cfg"
fi
}
function virsh_start_network {
local net=labs-$1
if $VIRSH net-info "$net" 2>/dev/null|grep -q "Active:.*no"; then
echo >&2 "Starting network $net."
$VIRSH net-start "$net"
fi
# Save, update, and restore iptables configuration made by libvirt
sudo iptables-save > "$LOG_DIR/iptables"
# Forward new connections, too (except on virbr0); this allows our
# NAT networks to talk to each other
sed -i -e '/FORWARD.*virbr[^0]/ s/ RELATED/ NEW,RELATED/' "$LOG_DIR/iptables"
sudo iptables-restore "$LOG_DIR/iptables"
}
function virsh_stop_network {
local net=labs-$1
# Undo our changes to iptables before letting libvirt deal with it
sudo iptables-save > "$LOG_DIR/iptables"
sed -i -e '/FORWARD.*virbr/ s/ NEW,RELATED/ RELATED/' "$LOG_DIR/iptables"
sudo iptables-restore "$LOG_DIR/iptables"
if $VIRSH net-info "$net" 2>/dev/null|grep -q "Active:.*yes"; then
echo >&2 "Stopping network $net."
$VIRSH net-destroy "$net"
fi
}
function virsh_undefine_network {
local net=labs-$1
if $VIRSH net-info "$net" >/dev/null 2>&1; then
echo >&2 "Undefining network $net."
$VIRSH net-undefine "$net"
fi
}
function vm_nic_base {
KVM_NET_OPTIONS="${KVM_NET_OPTIONS:-} --network bridge=virbr0"
}
function vm_nic_std {
local vm_name=$1
local index=$2
local netname=labs-$(ip_to_netname "${NODE_IF_IP[index]}")
KVM_NET_OPTIONS="${KVM_NET_OPTIONS:-} --network network=$netname"
}
function vm_nic_set_boot_prio {
: Not implemented
}
function create_network {
local index=$1
local net_name=${NET_NAME[index]}
local if_ip=${NET_GW[index]}
virsh_stop_network "$net_name"
virsh_undefine_network "$net_name"
virsh_define_network "$net_name" "$if_ip"
virsh_start_network "$net_name"
}
#-------------------------------------------------------------------------------
# Disk functions
#-------------------------------------------------------------------------------
function disk_exists {
local disk=$1
return $($VIRSH vol-info --pool "$KVM_VOL_POOL" "$disk" >/dev/null 2>&1)
}
function base_disk_exists {
disk_exists "$(get_base_disk_name)"
}
function disk_create {
local disk=$1
# Size in MB
local size=$2
if ! disk_exists "$disk"; then
$VIRSH vol-create-as "$KVM_VOL_POOL" "$disk" "${size}M" --format qcow2
fi
}
function disk_delete {
local disk=$1
if disk_exists "$disk"; then
$VIRSH vol-delete --pool "$KVM_VOL_POOL" "$disk"
fi
}
function base_disk_delete {
disk_delete "$(get_base_disk_name)"
}
# Use virt-sparsify to compress disk image and make it sparse
function disk_compress {
local disk_name=$1
local disk_path=$($VIRSH vol-path --pool "$KVM_VOL_POOL" "$disk_name")
local pool_dir=$(dirname "$disk_path")
local spexe
if ! spexe=$(which virt-sparsify); then
echo -e >&2 "${CError:-}No virt-sparsify executable found." \
"Consider installing libguestfs-tools.${CReset:-}"
return 0
fi
echo -e >&2 "${CStatus:-}Compressing disk image, input file:${CReset:-}"
sudo file "$disk_path"
sudo ls -lh "$disk_path"
sudo du -sh "$disk_path"
# virt-sparsify uses about 10 GB additional, temporary work space.
# The default (/tmp) is often too small (especially if it is a RAM
# disk). We use the pool_dir instead.
sudo "$spexe" --tmp "$pool_dir" --compress "$disk_path" \
"$pool_dir/.$disk_name"
# Copy owner and file modes from original file
sudo chown -v --reference="$disk_path" "$pool_dir/.$disk_name"
sudo chmod -v --reference="$disk_path" "$pool_dir/.$disk_name"
# Replace original with compressed file
sudo mv -vf "$pool_dir/.$disk_name" "$disk_path"
echo -e >&2 "${CStatus:-}Output file:${CReset:-}"
sudo file "$disk_path"
sudo ls -lh "$disk_path"
sudo du -sh "$disk_path"
}
#-------------------------------------------------------------------------------
# VM unregister, remove, delete
#-------------------------------------------------------------------------------
function vm_delete {
local vm_name=$1
echo >&2 -n "Asked to delete VM \"$vm_name\" "
if vm_exists "$vm_name"; then
echo >&2 "(found)"
vm_power_off "$vm_name"
# Take a break before undefining the VM
sleep 1
$VIRSH undefine --snapshots-metadata --remove-all-storage "$vm_name"
else
echo >&2 "(not found)"
fi
if disk_exists "$vm_name"; then
echo >&2 -e "${CInfo:-}Disk exists: ${CData:-}$vm_name${CReset:-}"
echo >&2 -e "Deleting disk $vm_name."
disk_delete "$vm_name"
fi
}
#-------------------------------------------------------------------------------
# Booting a VM and passing boot parameters
#-------------------------------------------------------------------------------
source "$OSBASH_LIB_DIR/kvm-keycodes.sh"
function _keyboard_push_scancode {
local vm_name=$1
shift
# Split string (e.g. '01 81') into arguments (works also if we
# get each hexbyte as a separate argument)
# Not quoting $@ is intentional -- we want to split on blanks
local scan_code=( $@ )
$VIRSH send-key "$vm_name" --codeset linux "${scan_code[@]}" >/dev/null
}
function vm_boot {
local vm_name=$1
echo -e >&2 "${CStatus:-}Starting VM ${CData:-}\"$vm_name\"${CReset:-}"
$VIRSH start "$vm_name"
}
#-------------------------------------------------------------------------------
# vim: set ai ts=4 sw=4 et ft=sh: