diff options
author | Aaron Ball <nullspoon@iohq.net> | 2016-02-19 19:54:51 -0700 |
---|---|---|
committer | Aaron Ball <nullspoon@iohq.net> | 2016-02-19 20:00:54 -0700 |
commit | 755aeab02ce534eb07b1e30ac2d91e6f09b27544 (patch) | |
tree | babcf817a897cd35c5c415dec61d4e4c3b7a630c | |
download | vmgr-755aeab02ce534eb07b1e30ac2d91e6f09b27544.tar.gz vmgr-755aeab02ce534eb07b1e30ac2d91e6f09b27544.tar.xz |
Initial commit
Currently builds out a vm. Assigns it a predictable mac address and
hostname. Also inserts a predictable ip reservation into dnsmasq for the
mac address. Part of the insertion process also adds dns entry, so
hostnames will resolve between systems.
Supports listing vms with ls action (vmgr ls). Output includes hostname
hostname, ip, and mac address.
Supports rm action to delete a virtual machine. The rm command will
stop the vm, delete it, remove its storage, refresh the storage pool,
and remove the vms ip address reservation.
-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 ${@} |