diff options
Diffstat (limited to 'vmgr')
-rwxr-xr-x | vmgr | 328 |
1 files changed, 328 insertions, 0 deletions
@@ -0,0 +1,328 @@ +#!/usr/bin/env bash +# +# Jei! Ég get að skrifa kóðann!! +# + +network='default' +prefixmac='52:54:00:7e:70:' +prefixip='192.168.122.' + + +binvirsh='/usr/bin/virsh' +binvirt_install='/usr/bin/virt-install' +binqemu_img='/usr/bin/qemu-img' +bincp='/bin/cp' + + +# +# Standard logging function. Prints standardized output. Timestamps all errors +# and infos. +# +# @param level Log level to print +# @param msg All remaining arguments to be printed as message +# +function log { + level=${1} + shift + msg=${@} + + d=$(date '+%F %T') + + oldifs=${IFS} + IFS="
" + + for i in ${msg}; do + if [[ ${level} == 'error' ]]; then + echo -n "${d} ERROR " + elif [[ ${level} == 'info' ]]; then + echo -n "${d} INFO " + elif [[ ${level} == 'debug' ]]; then + echo -n "${d} DEBUG " + elif [[ ${level} == 'fatal' ]]; then + echo -n "${d} FATAL " + fi + + echo -e ${i} + done + + IFS=${oldifs} + # Exit 1 if message was fatal + [[ ${1} == 'fatal' ]] && exit 1 +} + + +# +# Executes virt-install with the specified arguments. +# +function install { + local name=${1} + local disk=${2} + local mac=${3} + local mem=4096 + + out=$(${binvirt_install} \ + --name ${name} \ + --ram=${mem} \ + --virt-type=kvm \ + --disk "${disk}" \ + -w network=default,mac=${mac} \ + --graphics vnc,password=${name} \ + --noautoconsole \ + --import 2>&1) + + # Log the output of virt install + if [[ $? -gt 0 ]]; then + log error "${out}" + else + log info "${out}" + fi +} + + + +# +# Clones a disk. Supports two speed options, fast or small. +# Fast just does a simple copy of the disk from source to destination. +# Small uses qemu-img to recompress the disk from source to destination to save +# space. +# +# @param src +# @param dest +# @param speed +# +function clone_disk { + local src=${1} + local dest=${2} + local speed=${3} + + [[ -z ${1} ]] && echo "ERROR: Template path required (arg 1)." && exit 1 + [[ -z ${2} ]] && echo "ERROR: Destination disk path required (arg 2)." && exit 1 + [[ -z ${3} ]] && echo "ERROR: Clone speed required (arg 3)." && exit 1 + + if [[ ${speed} == 'fast' ]]; then + ${bincp} ${src} ${dest} + elif [[ ${speed} == 'small' ]]; then + ${binqemu_img} convert -O qcow2 -p -c ${src} ${dest} + else + log error "Unknown speed: ${speed}" + exit 1 + fi +} + + +# +# Reserves an ip on the specified network for mac address ${mac}. Also inserts +# dns entry for the specified name. +# +function net_reserve_ip { + local network=${1} + local name=${2} + local mac=${3} + local ip=${4} + + xml="<host mac='${mac}' name='${name}' ip='${ip}'/>" + # Perform reservation command + out=$(${binvirsh} net-update ${network} add ip-dhcp-host "${xml}" 2>&1) + + # Log the output of ip reservation operation + if [[ $? -gt 0 ]]; then + log error "A problem occured reserving ip address ${ip} for mac ${mac}." + log error "${out}" + exit 1 + else + log info "${out}" + fi +} + +# +# TODO +# +function net_rm_reservation { + local network=${1} + local name=${2} + local mac=${3} + local ip=${4} + + xml="<host mac='${mac}' name='${name}' ip='${ip}'/>" + # Perform reservation command + ${binvirsh} net-update ${network} delete ip-dhcp-host "${xml}" + + if [[ $? -gt 0 ]]; then + log error "A problem occured deleting ip reservation (${ip}) for mac ${mac}." + exit 1 + fi +} + + + +# +# Determines the next available vm index based on network mac address usage. +# +function get_next_index { + local network=${1} + local prefixmac=${2} + + # Start at 10, to allow for 0-9 as network-resources that aren't transient. + index=10 + + # Get list of hosts + hostxml="$(${binvirsh} net-dumpxml ${network} | grep '<host' )" + + # For each index, check if the mac address exists + while [ $(echo ${hostxml} | grep "mac='${prefixmac}${index}'" -c ) -ne 0 ]; do + #echo "Host exists at ${macprefix}${index}" + # Use this fancy printf statement to ensure we're always zero padded. + index=$(printf "%0*d" 2 $((${index} + 1))) + done + + # Unused index found. Echo. + echo "${index}" +} + + +# +# TODO +# +function get_index_from_name { + local name=${1} + + # Reset name so we grab whatever is at the front of an fqdn. If name isn't an + # fqdn, this will return the same. + name=$(echo ${name} | cut -d '.' -f 1) + + # Get length of the hostname + local len=$((${#name} - 2)) + + # Strip index off end of hostname + # Note that we assume a 2 digit + local index=${name:$len:2} + + # Return index + echo ${index} +} + + + +function vm_new { + [[ -z ${1} ]] && log fatal "A prefix name for the new vm is required." + [[ -z ${2} ]] && log fatal "A template disk path is required." + + local prefix=${1} + local template=${2} + local domain=${3} + + # Get next mac/index/ip + local next=$(get_next_index ${network} ${prefixmac}) + local mac="${prefixmac}${next}" + local ip="${prefixip}${next}" + local name="${prefix}${next}${domain}" + local disk="${name}.sda.qcow2" + + # echo -e "VM Variables:\n-------------" + # echo -e " \e[1mmac\e[0m: ${mac}" + # echo -e " \e[1mip\e[0m: ${ip}" + # echo -e " \e[1mname\e[0m: ${name}" + # echo -e " \e[1mdisk\e[0m: ${disk}" + # echo -e " \e[1mtemplate\e[0m: ${template}" + # echo + + # Clone the disk + log info "Cloning disk from ${template} to ${disk}" + clone_disk ${template} ${disk} 'fast' + + log info "Reserving ip address ${ip} for mac ${mac}" + net_reserve_ip ${network} ${name} ${mac} ${ip} + + log info "Installing VM into libvirtd." + install ${name} ${disk} ${mac} +} + + +function vm_rm { + [[ -z ${1} ]] && echo "Please a vm name to be deleted." && exit 1 + + local name=${1} + + # Get next mac/index/ip/disk + local index=$(get_index_from_name ${name}) + local mac="${prefixmac}${index}" + local ip="${prefixip}${index}" + local disk="${name}.sda.qcow2" + + # Get storage pool name (for refresh later) + local pool=$(basename $(pwd)) + + # Shut the VM down forcibly (it's about to be deleted, so we don't care about + # a friendly shutdown here) + ${binvirsh} destroy ${name} + + # Refresh the pool after VM destruction. If we don't do this, virsh + # intermittently fails on storage removal, with a permission denied error. + # This is because it doesn't know that the disk is no longer in use, so a + # pool-refresh is required. + log info "Refreshing storage pool ${pool} to ensure disk removal success." + poolout=$(${binvirsh} pool-refresh ${pool} 2>&1) + + if [[ $? -gt 0 ]]; then + log error "${poolout}" + else + log info "${poolout}" + fi + + # Delete the VM and remove all of its storage + log info -n "Removing VM ${name} and all storage." + undefout=$(${binvirsh} undefine --remove-all-storage ${name}) + + if [[ $? -gt 0 ]]; then + log error "${undefout}" + else + log info "${undefout}" + fi + + # Remove its network entry + log info "Deleting ip reservation for ${name}, mac ${mac}, ip ${ip}" + net_rm_reservation ${network} ${name} ${mac} ${ip} +} + + + +# +# Lists all VMs currently running, their expected ips, and mac addresses. +# +function vm_ls { + names=$(${binvirsh} list --name) + + output='Host IP MAC\n---- -- ---\n' + for i in ${names}; do + index=$(get_index_from_name ${i}) + output="${output}${i} ${prefixip}${index} ${prefixmac}${index}\n" + done + echo -e ${output} | column -c 80 -t +} + + +# +# Everyone loves a main function +# +function main { + action=${1} + shift; + + args=(${@}) + + if [[ ${action} == 'new' ]]; then + vm_new ${args[@]} + elif [[ ${action} == 'rm' ]]; then + vm_rm ${args[@]} + elif [[ ${action} == 'ls' ]]; then + vm_ls + elif [[ ${action} == '' ]]; then + log error "Please specify an action (new, rm, ls)" + exit 1 + else + log error "Unknown action: ${action}" + exit 1 + fi +} + +main ${@} |