training-guides/labs/lib/osbash/virtualbox.functions
Roger Luethi 67db6e7aa3 labs: fix Windows batch file creation
This changeset reverts "labs: temporary wbatch fix" and replaces it with
a proper fix, allowing the creation of correct Windows batch files on
build hosts without VirtualBox.

Change-Id: Iad005ce9f0dcecb910fc09b2c43385dd42992eda
2014-11-27 15:20:22 +01:00

739 lines
22 KiB
Bash

#-------------------------------------------------------------------------------
# VirtualBoxManage
#-------------------------------------------------------------------------------
VBM=vbm
: ${VBM_LOG:=$LOG_DIR/vbm.log}
# vbm is a wrapper around the VirtualBox VBoxManage executable; it handles
# logging and conditional execution (set OSBASH= to prevent the actual call to
# VBoxManage, or WBATCH= to keep a call from being recorded for Windows batch
# files)
function vbm {
${WBATCH:-:} wbatch_log_vbm "$@"
mkdir -p "$(dirname "$VBM_LOG")"
if [[ -n "${OSBASH:-}" ]]; then
echo "$@" >> "$VBM_LOG"
local rc=0
"$VBM_EXE" "$@" || rc=$?
if [ $rc -ne 0 ]; then
echo >&2 "FAILURE: VBoxManage: $@"
return 1
fi
else
echo "(not executed) $@" >> "$VBM_LOG"
fi
}
# Return VirtualBox version string (without distro extensions)
function get_vb_version {
local version=""
# e.g. 4.1.32r92798 4.3.10_RPMFusionr93012 4.3.10_Debianr93012
local raw=$(WBATCH= $VBM --version)
# Sanitize version string
local re='([0-9]+\.[0-9]+\.[0-9]+).*'
if [[ $raw =~ $re ]]; then
version=${BASH_REMATCH[1]}
fi
echo "$version"
}
#-------------------------------------------------------------------------------
# VM status
#-------------------------------------------------------------------------------
function vm_exists {
local vm_name=$1
return $(WBATCH= $VBM list vms | grep -q "\"$vm_name\"")
}
function vm_is_running {
local vm_name=$1
return $(WBATCH= $VBM showvminfo --machinereadable "$vm_name" | \
grep -q 'VMState="running"')
}
function vm_wait_for_shutdown {
local vm_name=$1
${WBATCH:-:} wbatch_wait_poweroff "$vm_name"
# Return if we are just faking it for wbatch
${OSBASH:+:} return 0
echo >&2 -n "Machine shutting down"
until WBATCH= $VBM showvminfo --machinereadable "$vm_name" 2>/dev/null | \
grep -q '="poweroff"'; do
echo -n .
sleep 1
done
echo >&2 -e "\nMachine powered off."
}
function vm_power_off {
local vm_name=$1
if vm_is_running "$vm_name"; then
echo >&2 "Powering off VM \"$vm_name\""
$VBM controlvm "$vm_name" poweroff
fi
# VirtualBox VM needs a break before taking new commands
vbox_sleep 1
}
function vm_snapshot {
local vm_name=$1
local shot_name=$2
$VBM snapshot "$vm_name" take "$shot_name"
# VirtualBox VM needs a break before taking new commands
vbox_sleep 1
}
#-------------------------------------------------------------------------------
# Host-only network functions
#-------------------------------------------------------------------------------
function hostonlyif_in_use {
local if_name=$1
return $(WBATCH= $VBM list -l runningvms | \
grep -q "Host-only Interface '$if_name'")
}
function ip_to_hostonlyif {
local ip=$1
local prevline=""
WBATCH= $VBM list hostonlyifs | grep -e "^Name:" -e "^IPAddress:" | \
while read line; do
if [[ "$line" == *$ip* ]]; then
# match longest string that ends with a space
echo ${prevline##Name:* }
break
fi
prevline=$line
done
}
function create_hostonlyif {
local out=$(WBATCH= $VBM hostonlyif create 2> /dev/null | grep "^Interface")
# out is something like "Interface 'vboxnet3' was successfully created"
local re="Interface '(.*)' was successfully created"
if [[ $out =~ $re ]]; then
echo "${BASH_REMATCH[1]}"
else
echo >&2 "Host-only interface creation failed"
return 1
fi
}
function create_network {
local ip=$1
# If we are here only for wbatch, ignore actual network interfaces; just
# return a unique identifier (so it can be replaced with the interface
# name used by Windows).
${OSBASH:+:} mktemp -u XXXXXXXX
${OSBASH:+:} return 0
local if_name="$(ip_to_hostonlyif "$ip")"
if [ -n "$if_name" ]; then
if hostonlyif_in_use "$if_name"; then
echo >&2 "Host-only interface $if_name ($ip) is in use." \
"Using it, too."
fi
else
echo >&2 "Creating host-only interface"
if_name=$(create_hostonlyif)
fi
echo >&2 "Configuring host-only network $ip ($if_name)"
$VBM hostonlyif ipconfig "$if_name" \
--ip "$ip" \
--netmask 255.255.255.0 >/dev/null
echo "$if_name"
}
#-------------------------------------------------------------------------------
# Disk functions
#-------------------------------------------------------------------------------
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# Creating, registering and unregistering disk images with VirtualBox
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# disk can be either a path or a disk UUID
function disk_registered {
local disk=$1
return $(WBATCH= $VBM list hdds | grep -q "$disk")
}
# disk can be either a path or a disk UUID
function disk_unregister {
local disk=$1
echo >&2 -e "Unregistering disk\n\t$disk"
$VBM closemedium disk "$disk"
}
function create_vdi {
local hd_path=$1
local size=$2
echo >&2 -e "Creating disk:\n\t$hd_path"
$VBM createhd --format VDI --filename "$hd_path" --size "$size"
}
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# Attaching and detaching disks from VMs
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# disk can be either a path or a disk UUID
function get_next_child_uuid {
local disk=$1
local child_uuid=""
local line=""
if disk_registered "$disk"; then
line=$(WBATCH= $VBM showhdinfo "$disk" | grep -e "^Child UUIDs:")
child_uuid=${line##Child UUIDs:* }
fi
echo -e "next_child_uuid $disk:\n\t$line\n\t$child_uuid" >> "$VBM_LOG"
echo "$child_uuid"
}
# disk can be either a path or a disk UUID
function path_to_disk_uuid {
local disk=$1
local uuid=""
local line=$(WBATCH= $VBM showhdinfo "$disk" | grep -e "^UUID:")
local re='UUID:[ ]+([^ ]+)'
if [[ $line =~ $re ]]; then
uuid=${BASH_REMATCH[1]}
fi
echo -e "path_to_disk_uuid $disk:\n\t$line\n\t$uuid" >> "$VBM_LOG"
echo "$uuid"
}
# disk can be either a path or a disk UUID
function disk_to_path {
local disk=$1
local fpath=""
local line=$(WBATCH= $VBM showhdinfo "$disk" | grep -e "^Location:")
local re='Location:[ ]+([^ ]+)'
if [[ $line =~ $re ]]; then
fpath=${BASH_REMATCH[1]}
fi
echo -e "disk_to_path $disk:\n\t$line\n\t$fpath" >> "$VBM_LOG"
echo "$fpath"
}
# disk can be either a path or a disk UUID
function disk_to_vm {
local disk=$1
local vm_name=""
local line=$(WBATCH= $VBM showhdinfo "$disk" | grep -e "^In use by VMs:")
local re='In use by VMs:[ ]+([^ ]+) '
if [[ $line =~ $re ]]; then
vm_name=${BASH_REMATCH[1]}
fi
echo -e "disk_to_vm $disk:\n\t$line\n\t$vm_name" >> "$VBM_LOG"
echo "$vm_name"
}
function vm_get_disk_path {
local vm_name=$1
local line=$(WBATCH= $VBM showvminfo --machinereadable "$vm_name" | \
grep '^"SATA-0-0"=.*vdi"$')
local hd_path=${line##\"SATA-0-0\"=\"}
hd_path=${hd_path%\"}
echo "$hd_path"
}
function vm_detach_disk {
local vm_name=$1
echo >&2 "Detaching disk from VM \"$vm_name\""
$VBM storageattach "$vm_name" \
--storagectl SATA \
--port 0 \
--device 0 \
--type hdd \
--medium none
# VirtualBox VM needs a break before taking new commands
vbox_sleep 1
}
# disk can be either a path or a disk UUID
function vm_attach_disk {
local vm_name=$1
local disk=$2
echo >&2 -e "Attaching to VM \"$vm_name\":\n\t$disk"
$VBM storageattach "$vm_name" \
--storagectl SATA \
--port 0 \
--device 0 \
--type hdd \
--medium "$disk"
}
# disk can be either a path or a disk UUID
function vm_attach_disk_multi {
local vm_name=$1
local disk=$2
$VBM modifyhd --type multiattach "$disk"
echo >&2 -e "Attaching to VM \"$vm_name\":\n\t$disk"
$VBM storageattach "$vm_name" \
--storagectl SATA \
--port 0 \
--device 0 \
--type hdd \
--medium "$disk"
}
#-------------------------------------------------------------------------------
# VM create and configure
#-------------------------------------------------------------------------------
function vm_mem {
local vm_name="$1"
local mem="$2"
$VBM modifyvm "$vm_name" --memory "$mem"
}
function vm_cpus {
local vm_name="$1"
local cpus="$2"
$VBM modifyvm "$vm_name" --cpus "$cpus"
}
# Port forwarding from host to VM (binding to host's 127.0.0.1)
function vm_port {
local vm_name="$1"
local desc="$2"
local hostport="$3"
local guestport="$4"
$VBM modifyvm "$vm_name" \
--natpf1 "$desc,tcp,127.0.0.1,$hostport,,$guestport"
}
function vm_nic_hostonly {
local vm_name=$1
# We start counting interfaces at 0, but VirtualBox starts NICs at 1
local nic=$(($2 + 1))
local net_name=$3
$VBM modifyvm "$vm_name" \
"--nictype$nic" "$NICTYPE" \
"--nic$nic" hostonly \
"--hostonlyadapter$nic" "$net_name" \
"--nicpromisc$nic" allow-all
}
function vm_nic_nat {
local vm_name=$1
# We start counting interfaces at 0, but VirtualBox starts NICs at 1
local nic=$(($2 + 1))
$VBM modifyvm "$vm_name" "--nictype$nic" "$NICTYPE" "--nic$nic" nat
}
function vm_create {
# NOTE: We assume that a VM with a matching name is ours.
# Remove and recreate just in case someone messed with it.
local vm_name=$1
${WBATCH:-:} wbatch_abort_if_vm_exists "$vm_name"
# Don't write to wbatch scripts, and don't execute when we are faking it
# it for wbatch
WBATCH= ${OSBASH:-:} vm_delete "$vm_name"
# XXX ostype is distro-specific; moving it to modifyvm disables networking
# Note: The VirtualBox GUI may not notice group changes after VM creation
# until GUI is restarted. Moving a VM with group membership will
# fail in cases (lingering files from old VM) where creating a
# VM in that location succeeds.
#
# XXX temporary hack
# --groups not supported in VirtualBox 4.1 (Mac OS X 10.5)
echo >&2 "Creating VM \"$vm_name\""
local ver=$(get_vb_version)
if [[ $ver = 4.1* ]]; then
$VBM createvm \
--name "$vm_name" \
--register \
--ostype Ubuntu_64 >/dev/null
else
$VBM createvm \
--name "$vm_name" \
--register \
--ostype Ubuntu_64 \
--groups "/$VM_GROUP" >/dev/null
fi
$VBM modifyvm "$vm_name" --rtcuseutc on
$VBM modifyvm "$vm_name" --biosbootmenu disabled
$VBM modifyvm "$vm_name" --largepages on
$VBM modifyvm "$vm_name" --boot1 disk
# XXX temporary hack
# --portcount not supported in VirtualBox 4.1 (Mac OS X 10.5)
if [[ $ver == 4.1* ]]; then
$VBM storagectl "$vm_name" --name SATA --add sata
else
$VBM storagectl "$vm_name" --name SATA --add sata --portcount 1
fi
$VBM storagectl "$vm_name" --name SATA --hostiocache on
$VBM storagectl "$vm_name" --name IDE --add ide
echo >&2 "Created VM \"$vm_name\""
}
#-------------------------------------------------------------------------------
# VM export
#-------------------------------------------------------------------------------
# Export node VMs to OVA package file
function vm_export_ova {
local ova_file=$1
local nodes=$2
echo >&2 "Removing shared folders for export"
local -a share_paths
local node
for node in $nodes; do
local share_path=$(vm_get_share_path "$node")
share_paths+=("$share_path")
if [ -n "$share_path" ]; then
vm_rm_share "$node" "$SHARE_NAME"
fi
done
rm -f "$ova_file"
mkdir -pv "$IMG_DIR"
$VBM export $nodes --output "$ova_file"
echo >&2 "Appliance exported"
echo >&2 "Reattaching shared folders"
local ii=0
for node in $nodes; do
if [ -n "${share_paths[$ii]}" ]; then
vm_add_share "$node" "${share_paths[$ii]}" "$SHARE_NAME"
fi
ii=$(($ii + 1))
done
}
# Export node VMs by cloning VMs to directory
function vm_export_dir {
local export_dir=$1
local nodes=$2
rm -rvf "$export_dir"
for node in $nodes; do
if vm_is_running "$node"; then
echo "Powering off node VM $node."
echo "$VBM controlvm $node poweroff"
$VBM controlvm "$node" poweroff
fi
sleep 1
local share_path=$(vm_get_share_path "$node")
if [ -n "$share_path" ]; then
echo >&2 "Removing shared folder for export"
vm_rm_share "$node" "$SHARE_NAME"
fi
sleep 1
echo "Exporting VM $node to $export_dir"
# Use all: machineandchildren works only if --snapshot is given as UUID
$VBM clonevm "$node" \
--mode all \
--options keepallmacs,keepdisknames \
--name "$node-e" \
--groups "/$VM_GROUP" \
--basefolder "$export_dir" \
--register
# VirtualBox registers disks and snapshots of the clone VM even if we
# don't register the VM above. Unregistering the registered VM takes
# care of the snapshots, but we still have to unregister the clone
# basedisk.
local snapshot_path="$(vm_get_disk_path "$node-e")"
local hd_dir=${snapshot_path%Snapshots/*}
local hd_path=$hd_dir$(get_base_disk_name)
$VBM unregistervm "$node-e"
if [ -n "$hd_path" ]; then
disk_unregister "$hd_path"
fi
if [ -n "$share_path" ]; then
echo >&2 "Reattaching shared folder"
vm_add_share "$node" "$share_path" "$SHARE_NAME"
fi
done
}
#-------------------------------------------------------------------------------
# VM unregister, remove, delete
#-------------------------------------------------------------------------------
function vm_unregister_del {
local vm_name=$1
echo >&2 "Unregistering and deleting VM \"$vm_name\""
$VBM unregistervm "$vm_name" --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"
local hd_path="$(vm_get_disk_path "$vm_name")"
if [ -n "$hd_path" ]; then
echo >&2 -e "Disk attached: $hd_path"
vm_detach_disk "$vm_name"
disk_unregister "$hd_path"
echo >&2 -e "Deleting: $hd_path"
rm -f "$hd_path"
fi
vm_unregister_del "$vm_name"
else
echo >&2 "(not found)"
fi
}
# Remove VMs using disk and its children disks
# disk can be either a path or a disk UUID
function disk_delete_child_vms {
local disk=$1
if ! disk_registered "$disk"; then
# VirtualBox doesn't know this disk; we are done
echo >&2 -e "Disk not registered with VirtualBox:\n\t$disk"
return 0
fi
# XXX temporary hack
# No Child UUIDs through showhdinfo in VirtualBox 4.1 (Mac OS X 10.5)
local ver=$(get_vb_version)
if [[ $ver == 4.1* ]]; then
local vm_name=""
for vm_name in controller network compute base; do
vm_delete "$vm_name"
done
return 0
fi
while [ : ]; do
local child_uuid=$(get_next_child_uuid "$disk")
if [ -n "$child_uuid" ]; then
local child_disk="$(disk_to_path "$child_uuid")"
echo >&2 -e "\nChild disk UUID: $child_uuid\n\t$child_disk"
local vm_name="$(disk_to_vm "$child_uuid")"
if [ -n "$vm_name" ]; then
echo 2>&1 -e "\tstill attached to VM \"$vm_name\""
vm_delete "$vm_name"
else
echo >&2 "Unregistering and deleting: $child_uuid"
disk_unregister "$child_uuid"
echo >&2 -e "\t$child_disk"
rm -f "$child_disk"
fi
else
break
fi
done
}
#-------------------------------------------------------------------------------
# VM shared folders
#-------------------------------------------------------------------------------
# Return the host path for a VM's shared directory; assumes there is only one.
function vm_get_share_path {
local vm_name=$1
local line=$(WBATCH= $VBM showvminfo --machinereadable "$vm_name" | \
grep '^SharedFolderPathMachineMapping1=')
local share_path=${line##SharedFolderPathMachineMapping1=\"}
share_path=${share_path%\"}
echo "$share_path"
}
function vm_add_share_automount {
local vm_name=$1
local share_dir=$2
local share_name=$3
$VBM sharedfolder add "$vm_name" \
--name "$share_name" \
--hostpath "$share_dir" \
--automount
}
function vm_add_share {
local vm_name=$1
local share_dir=$2
local share_name=$3
$VBM sharedfolder add "$vm_name" \
--name "$share_name" \
--hostpath "$share_dir"
}
function vm_rm_share {
local vm_name=$1
local share_name=$2
$VBM sharedfolder remove "$vm_name" --name "$share_name"
}
#-------------------------------------------------------------------------------
# VirtualBox guest add-ons
#-------------------------------------------------------------------------------
# Download VirtualBox guest-additions. Returns local path of ISO image.
function _download_guestadd-iso {
local iso=VBoxGuestAdditions.iso
local ver=$(get_vb_version)
if [[ -n "$ver" ]]; then
local url="http://download.virtualbox.org/virtualbox/$ver/VBoxGuestAdditions_$ver.iso"
download "$url" "$ISO_DIR" $iso
fi
echo "$ISO_DIR/$iso"
}
function _get_guestadd-iso {
local iso=VBoxGuestAdditions.iso
local add_iso="$IMG_DIR/$iso"
if [ -f "$add_iso" ]; then
echo "$add_iso"
return 0
fi
add_iso="/Applications/VirtualBox.app/Contents/MacOS/$iso"
if [ -f "$add_iso" ]; then
echo "$add_iso"
return 0
fi
echo >&2 "Searching filesystem for VBoxGuestAdditions. This may take a while..."
add_iso=$(find / -name "$iso" 2>/dev/null) || true
if [ -n "$add_iso" ]; then
echo "$add_iso"
return 0
fi
echo >&2 "Looking on the Internet"
add_iso=$(_download_guestadd-iso)
if [ -f "$add_iso" ]; then
echo "$add_iso"
return 0
fi
}
function _vm_attach_guestadd-iso {
local vm_name=$1
local guestadd_iso=$2
local rc=0
$VBM storageattach "$vm_name" --storagectl IDE --port 1 --device 0 --type dvddrive --medium "$guestadd_iso" 2>/dev/null || rc=$?
return $rc
}
function vm_attach_guestadd-iso {
local vm_name=$1
OSBASH= ${WBATCH:-:} _vm_attach_guestadd-iso "$vm_name" emptydrive
OSBASH= ${WBATCH:-:} _vm_attach_guestadd-iso "$vm_name" additions
# Return if we are just faking it for wbatch
${OSBASH:+:} return 0
if [ -z "${GUESTADD_ISO-}" ]; then
# No location provided, asking VirtualBox for one
# An existing drive is needed to make additions shortcut work
# (at least VirtualBox 4.3.12 and below)
WBATCH= _vm_attach_guestadd-iso "$vm_name" emptydrive
if WBATCH= _vm_attach_guestadd-iso "$vm_name" additions; then
echo >&2 "Using VBoxGuestAdditions provided by VirtualBox"
return 0
fi
# Neither user nor VirtualBox are helping, let's go guessing
GUESTADD_ISO=$(_get_guestadd-iso)
if [ -z "GUESTADD_ISO" ]; then
# No ISO found
return 2
fi
fi
if WBATCH= _vm_attach_guestadd-iso "$vm_name" "$GUESTADD_ISO"; then
echo >&2 "Attached $GUESTADD_ISO"
return 0
else
echo >&2 "Failed to attach $GUESTADD_ISO"
return 3
fi
}
#-------------------------------------------------------------------------------
# Sleep
#-------------------------------------------------------------------------------
function vbox_sleep {
sec=$1
# Don't sleep if we are just faking it for wbatch
${OSBASH:-:} sleep "$sec"
${WBATCH:-:} wbatch_sleep "$sec"
}
#-------------------------------------------------------------------------------
# Booting a VM and passing boot parameters
#-------------------------------------------------------------------------------
source "$OSBASH_LIB_DIR/scanlib"
function _vbox_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=( $@ )
$VBM controlvm "$vm_name" keyboardputscancode "${scan_code[@]}"
}
function vbox_kbd_escape_key {
local vm_name=$1
_vbox_push_scancode "$vm_name" "$(esc2scancode)"
}
function vbox_kbd_enter_key {
local vm_name=$1
_vbox_push_scancode "$vm_name" "$(enter2scancode)"
}
function vbox_kbd_string_input {
local vm_name=$1
local str=$2
# This loop is inefficient enough that we don't overrun the keyboard input
# buffer when pushing scancodes to the VirtualBox.
while IFS= read -r -n1 char; do
if [ -n "$char" ]; then
SC=$(char2scancode "$char")
if [ -n "$SC" ]; then
_vbox_push_scancode "$vm_name" "$SC"
else
echo >&2 "not found: $char"
fi
fi
done <<< "$str"
}
function vbox_boot {
local vm_name=$1
echo >&2 "Starting VM \"$vm_name\""
if [ -n "${VM_UI:-}" ]; then
$VBM startvm "$vm_name" --type "$VM_UI"
else
$VBM startvm "$vm_name"
fi
}
#-------------------------------------------------------------------------------
# vim: set ai ts=4 sw=4 et ft=sh: