diff --git a/README b/README index 300729d..d4c0e9a 100644 --- a/README +++ b/README @@ -3,3 +3,4 @@ Website: http://www.virtuallyghetto.com/ ghettoVCB Documentation - http://communities.vmware.com/docs/DOC-8760 ghettoVCB VMTN Group - http://communities.vmware.com/groups/ghettovcb +ghettoVCB Restore Documentation - http://communities.vmware.com/docs/DOC-10595 diff --git a/ghettoVCB-restore.sh b/ghettoVCB-restore.sh new file mode 100755 index 0000000..209119f --- /dev/null +++ b/ghettoVCB-restore.sh @@ -0,0 +1,391 @@ +# Author: William Lam +# 08/18/2009 +# http://www.virtuallyghetto.com/ +################################################################## + +###### DO NOT EDIT PASS THIS LINE ###### + +LAST_MODIFIED_DATE=2011_11_19 +VERSION=1 +VERSION_STRING=${LAST_MODIFIED_DATE}_${VERSION} + +printUsage() { + echo "###############################################################################" + echo "#" + echo "# ghettoVCB-restore for ESX/ESXi 3.5, 4.x+ and 5.0" + echo "# Author: William Lam" + echo "# http://www.virtuallyghetto.com/" + echo "# Documentation: http://communities.vmware.com/docs/DOC-8760" + echo "# Created: 08/18/2009" + echo "# Last modified: ${VERSION_STRING}" + echo "#" + echo "###############################################################################" + echo + echo "Usage: $0 -c [VM_BACKUP_UP_LIST] -l [LOG_FILE] -d [DRYRUN_DEBUG_INFO]" + echo + echo "OPTIONS:" + echo " -c VM backup list" + echo " -l File ot output logging" + echo " -d Dryrun/Debug Info [1|2]" + echo + echo "(e.g.)" + echo -e "\nOutput will go to stdout" + echo -e "\t$0 -c vms_to_restore " + echo -e "\nOutput will log to /tmp/ghettoVCB-restore.log" + echo -e "\t$0 -c vms_to_restore -l /tmp/ghettoVCB-restore.log" + echo -e "\nDryrun/Debug Info (dryrun only)" + echo -e "\t$0 -c vms_to_restore -d 1" + echo -e "\t$0 -c vms_to_restore -d 2" + echo + exit 1 +} + +logger() { + MSG=$1 + if [ "${LOG_TO_STDOUT}" -eq 1 ]; then + echo -e "${MSG}" + else + echo -e "${MSG}" >> "${LOG_OUTPUT}" + fi +} + +sanityCheck() { + NUM_OF_ARGS=$1 + + if [[ ${NUM_OF_ARGS} -ne 2 ]] && [[ ${NUM_OF_ARGS} -ne 4 ]] && [[ ${NUM_OF_ARGS} -ne 6 ]]; then + printUsage + fi + + #log to stdout or to logfile + if [ -z "${LOG_OUTPUT}" ]; then + LOG_TO_STDOUT=1 + REDIRECT=/dev/null + else + LOG_TO_STDOUT=0 + REDIRECT=${LOG_OUTPUT} + echo "Logging output to \"${LOG_OUTPUT}\" ..." + touch "${LOG_OUTPUT}" + fi + + if [[ "${DEVEL_MODE}" == "1" ]] && [[ "${DEVEL_MODE}" == "2" ]] && [[ "${DEVEL_MODE}" == "0" ]]; then + DEVEL_MODE=0 + fi + + if [ -f /usr/bin/vmware-vim-cmd ]; then + VMWARE_CMD=/usr/bin/vmware-vim-cmd + VMKFSTOOLS_CMD=/usr/sbin/vmkfstools + elif [ -f /bin/vim-cmd ]; then + VMWARE_CMD=/bin/vim-cmd + VMKFSTOOLS_CMD=/sbin/vmkfstools + else + logger "ERROR: Unable to locate *vimsh*! You're not running ESX(i) 3.5+, 4.x, 5.x!" + echo "ERROR: Unable to locate *vimsh*! You're not running ESX(i) 3.5+, 4.x, 5.x!" + exit + fi + + ESX_VERSION=$(vmware -v | awk '{print $3}') + if [ "${ESX_VERSION}" == "5.0.0" ]; then + VER=5 + elif [[ "${ESX_VERSION}" == "4.0.0" ]] || [[ "${ESX_VERSION}" == "4.1.0" ]]; then + VER=4 + else + ESX_VERSION=$(vmware -v | awk '{print $4}') + if [[ "${ESX_VERSION}" == "3.5.0" ]] || [[ "${ESX_VERSION}" == "3i" ]]; then + VER=3 + else + echo "You're not running ESX(i) 3.5, 4.x, 5.x!" + exit 1 + fi + fi + + if [ ! "`whoami`" == "root" ]; then + logger "ERROR: This script needs to be executed by \"root\"!" + echo "ERROR: This script needs to be executed by \"root\"!" + exit 1 + fi + + #ensure input file exists + if [ ! -f "${CONFIG_FILE}" ]; then + logger "ERROR: \"${CONFIG_FILE}\" input file does not exists\n" + echo -e "ERROR: \"${CONFIG_FILE}\" input file does not exists\n" + exit 1 + fi +} + +startTimer() { + START_TIME=$(date) + S_TIME=$(date +%s) +} + +endTimer() { + END_TIME=$(date) + E_TIME=$(date +%s) + logger "\nStart time: ${START_TIME}" + logger "End time: ${END_TIME}" + DURATION=$(echo $((E_TIME - S_TIME))) + + #calculate overall completion time + if [ ${DURATION} -le 60 ]; then + logger "Duration : ${DURATION} Seconds" + else + logger "Duration : $(awk 'BEGIN{ printf "%.2f\n", '${DURATION}'/60}') Minutes\n" + fi + logger "\n---------------------------------------------------------------------------------------------------------------\n" + echo +} + +ghettoVCBrestore() { + VM_FILE=$1 + + startTimer + + ORIG_IFS=${IFS} + IFS=' +' + for LINE in $(cat "${VM_FILE}" | sed '/^$/d' | sed -e '/^#/d' | sed -e 's/^[[:blank:]]*//;s/[[:blank:]]*$//'); + do + VM_TO_RESTORE=$(echo "${LINE}" | awk -F ';' '{print $1}' | sed 's/"//g' | sed -e 's/^[[:blank:]]*//;s/[[:blank:]]*$//') + DATASTORE_TO_RESTORE_TO=$(echo "${LINE}" | awk -F ';' '{print $2}' | sed 's/"//g' | sed -e 's/^[[:blank:]]*//;s/[[:blank:]]*$//') + RESTORE_DISK_FORMAT=$(echo "${LINE}" | awk -F ';' '{print $3}' | sed 's/"//g' | sed -e 's/^[[:blank:]]*//;s/[[:blank:]]*$//') + + #figure the disk format to use + if [ "${RESTORE_DISK_FORMAT}" -eq 1 ]; then + FORMAT_STRING=zeroedthick + elif [ "${RESTORE_DISK_FORMAT}" -eq 2 ]; then + FORMAT_STRING=2gbsparse + elif [ "${RESTORE_DISK_FORMAT}" -eq 3 ]; then + FORMAT_STRING=thin + elif [ "${RESTORE_DISK_FORMAT}" -eq 4 ]; then + FORMAT_STRING=eagerzeroedthick + fi + + IS_DIR=0 + #supports DIR or .TGZ from ghettoVCB.sh ONLY! + if [ -d "${VM_TO_RESTORE}" ]; then + #figure out the contents of the directory (*.vmdk,*-flat.vmdk,*.vmx) + VM_VMX=$(ls "${VM_TO_RESTORE}" | grep ".vmx") + VM_VMDK_DESCRS=$(ls "${VM_TO_RESTORE}" | grep ".vmdk" | grep -v "\-flat.vmdk") + VMDKS_FOUND=$(grep -iE '(scsi|ide)' "${VM_TO_RESTORE}/${VM_VMX}" | grep -i fileName | awk -F " " '{print $1}') + VM_DISPLAY_NAME=$(grep -i "displayName" "${VM_TO_RESTORE}/${VM_VMX}" | awk -F '=' '{print $2}' | sed 's/"//g' | sed -e 's/^[[:blank:]]*//;s/[[:blank:]]*$//') + VM_ORIG_FOLDER_NAME=$(echo "${VM_TO_RESTORE##*/}") + VM_FOLDER_NAME=$(echo "${VM_ORIG_FOLDER_NAME}" | sed 's/-[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]--[0-1]*//g') + + #figure out the VMDK rename, esepcially important if original backup had VMDKs spread across multiple datastores + #restoration will not support that since I can't assume the original system will be availabl with same ds/etc. + #files will be restored to single VMFS volume without disrupting original backup + + NUM_OF_VMDKS=0 + TMP_IFS=${IFS} + IFS=${ORIG_IFS} + for VMDK in ${VMDKS_FOUND}; + do + #extract the SCSI ID and use it to check for valid vmdk disk + SCSI_ID=$(echo ${VMDK%%.*}) + grep -i "${SCSI_ID}.present" "${VM_TO_RESTORE}/${VM_VMX}" | grep -i "true" > /dev/null 2>&1 + #if valid, then we use the vmdk file + if [ $? -eq 0 ]; then + grep -i "${SCSI_ID}.deviceType" "${VM_TO_RESTORE}/${VM_VMX}" | grep -i "scsi-hardDisk" > /dev/null 2>&1 + #if we find the device type is of scsi-disk, then proceed + if [ $? -eq 0 ]; then + DISK=$(grep -i ${SCSI_ID}.fileName "${VM_TO_RESTORE}/${VM_VMX}") + else + #if the deviceType is NULL for IDE which it is, thanks for the inconsistency VMware + #we'll do one more level of verification by checking to see if an ext. of .vmdk exists + #since we can not rely on the deviceType showing "ide-hardDisk" + grep -i ${SCSI_ID}.fileName "${VM_TO_RESTORE}/${VM_VMX}" | grep -i ".vmdk" > /dev/null 2>&1 + if [ $? -eq 0 ]; then + DISK=$(grep -i ${SCSI_ID}.fileName "${VM_TO_RESTORE}/${VM_VMX}") + fi + fi + + + if [ "${DISK}" != "" ]; then + SCSI_CONTROLLER=$(echo ${DISK} | awk -F '=' '{print $1}') + RENAME_DESTINATION_LINE_VMDK_DISK="${SCSI_CONTROLLER} = \"${VM_DISPLAY_NAME}-${NUM_OF_VMDKS}.vmdk\"" + if [ -z "${VMDK_LIST_TO_MODIFY}" ]; then + VMDK_LIST_TO_MODIFY="${DISK},${RENAME_DESTINATION_LINE_VMDK_DISK}" + else + VMDK_LIST_TO_MODIFY="${VMDK_LIST_TO_MODIFY};${DISK},${RENAME_DESTINATION_LINE_VMDK_DISK}" + fi + DISK='' + fi + fi + NUM_OF_VMDKS=$((NUM_OF_VMDKS+1)) + done + IFS=${TMP_IFS} + else + logger "Support for .tgz not supported - \"${VM_TO_RESTORE}\" will not be backed up!" + IS_TGZ=1 + fi + +if [ ! "${IS_TGZ}" == "1" ]; then + if [ "${DEVEL_MODE}" == "1" ]; then + logger "\n################ DEBUG MODE ##############" + logger "Virtual Machine: \"${VM_DISPLAY_NAME}\"" + logger "VM_VMX: \"${VM_VMX}\"" + logger "VM_ORG_FOLDER: \"${VM_ORIG_FOLDER_NAME}\"" + logger "VM_FOLDER_NAME: \"${VM_FOLDER_NAME}\"" + logger "VMDK_LIST_TO_MODIFY:" + OLD_IFS="${IFS}" + IFS=";" + for i in ${VMDK_LIST_TO_MODIFY} + do + VMDK_1=$(echo $i | awk -F ',' '{print $1}') + VMDK_2=$(echo $i | awk -F ',' '{print $2}') + logger "${VMDK_1}" + logger "${VMDK_2}" + done + unset IFS + IFS="${OLD_IFS}" + logger "##########################################\n" + else + #validates the datastore to restore is valid and available + if [ ! -d "${DATASTORE_TO_RESTORE_TO}" ]; then + logger "ERROR: Unable to verify datastore locateion: \"${DATASTORE_TO_RESTORE_TO}\"! Ensure this exists" + #validates that all 4 required variables are defined before continuing + elif [[ -z "${VM_VMX}" ]] && [[ -z "${VM_VMDK_DESCRS}" ]] && [[ -z "${VM_DISPLAY_NAME}" ]] && [[ -z "${VM_FOLDER_NAME}" ]]; then logger "ERROR: Unable to define all required variables: VM_VMX, VM_VMDK_DESCR and VM_DISPLAY_NAME!" + #validates that a directory with the same VM does not already exists + elif [ -d "${DATASTORE_TO_RESTORE_TO}/${VM_FOLDER_NAME}" ]; then + logger "ERROR: Directory \"${DATASTORE_TO_RESTORE_TO}/${VM_FOLDER_NAME}\" looks like it already exists, please check contents and remove directory before trying to restore!" + else + logger "################## Restoring VM: $VM_DISPLAY_NAME #####################" + if [ "${DEVEL_MODE}" == "2" ]; then + logger "==========> DEBUG MODE LEVEL 2 ENABLED <==========" + fi + logger "Start time: $(date)" + logger "Restoring VM from: \"${VM_TO_RESTORE}\"" + logger "Restoring VM to Datastore: \"${DATASTORE_TO_RESTORE_TO}\" using Disk Format: \"${FORMAT_STRING}\"" + + VM_RESTORE_DIR="${DATASTORE_TO_RESTORE_TO}/${VM_FOLDER_NAME}" + + #create VM folder on datastore if it doesn't already exists + logger "Creating VM directory: \"${VM_RESTORE_DIR}\" ..." + if [ ! "${DEVEL_MODE}" == "2" ]; then + mkdir -p "${VM_RESTORE_DIR}" + fi + + #copy .vmx file + logger "Copying \"${VM_VMX}\" file ..." + if [ ! "${DEVEL_MODE}" == "2" ]; then + cp "${VM_TO_RESTORE}/${VM_VMX}" "${VM_RESTORE_DIR}/${VM_VMX}" + fi + + #loop through all VMDK(s) and vmkfstools copy to destination + logger "Restoring VM's VMDK(s) ..." + #MAX=${#ORIGINAL_VMX_VMDK_LINES[*]} + OLD_IFS="${IFS}" + IFS=";" + for i in ${VMDK_LIST_TO_MODIFY} + do + #retrieve individual VMDKs + SOURCE_LINE_VMDK=$(echo "${i}" | awk -F ',' '{print $1}' | awk -F '=' '{print $2}' | sed 's/"//g' | sed -e 's/^[[:blank:]]*//;s/[[:blank:]]*$//') + DESTINATION_LINE_VMDK=$(echo "${i}" | awk -F ',' '{print $2}' | awk -F '=' '{print $2}' | sed 's/"//g' | sed -e 's/^[[:blank:]]*//;s/[[:blank:]]*$//') + #retrieve individual VMDK lines in .vmx file to update + ORIGINAL_VMX_LINE=$(echo "${i}" | awk -F ',' '{print $1}') + MODIFIED_VMX_LINE=$(echo "${i}" | awk -F ',' '{print $2}') + + #update restored VM to match VMDKs + logger "Updating VMDK entry in \"${VM_VMX}\" file ..." + if [ ! "${DEVEL_MODE}" == "2" ]; then + sed -i "s#${ORIGINAL_VMX_LINE}#${MODIFIED_VMX_LINE}#g" "${VM_RESTORE_DIR}/${VM_VMX}" + fi + + echo "${SOURCE_LINE_VMDK}" | grep "/vmfs/volumes" > /dev/null 2>&1 + if [ $? -eq 0 ]; then + #SOURCE_VMDK="${SOURCE_LINE_VMDK}" + DS_VMDK_PATH=$(echo "${SOURCE_LINE_VMDK}" | sed 's/\/vmfs\/volumes\///g') + VMDK_DATASTORE=$(echo "${DS_VMDK_PATH%%/*}") + VMDK_VM=$(echo "${DS_VMDK_PATH##*/}") + SOURCE_VMDK="${VM_TO_RESTORE}/${VMDK_DATASTORE}/${VMDK_VM}" + else + SOURCE_VMDK="${VM_TO_RESTORE}/${SOURCE_LINE_VMDK}" + fi + DESTINATION_VMDK="${VM_RESTORE_DIR}/${DESTINATION_LINE_VMDK}" + + if [ ! "${DEVEL_MODE}" == "2" ]; then + ADAPTER_FORMAT=$(grep -i "ddb.adapterType" "${SOURCE_VMDK}" | awk -F "=" '{print $2}' | sed -e 's/^[[:blank:]]*//;s/[[:blank:]]*$//;s/"//g') + + if [ ${RESTORE_DISK_FORMAT} -eq 1 ]; then + if [[ "${VER}" == "4" ]] || [[ "${VER}" == "5" ]]; then + ${VMKFSTOOLS_CMD} -i "${SOURCE_VMDK}" -a "${ADAPTER_FORMAT}" -d zeroedthick "${DESTINATION_VMDK}" 2>&1 | tee "${REDIRECT}" + else + ${VMKFSTOOLS_CMD} -i "${SOURCE_VMDK}" -a "${ADAPTER_FORMAT}" "${DESTINATION_VMDK}" 2>&1 | tee "${REDIRECT}" + fi + elif [ ${RESTORE_DISK_FORMAT} -eq 2 ]; then + ${VMKFSTOOLS_CMD} -i "${SOURCE_VMDK}" -a "${ADAPTER_FORMAT}" -d 2gbsparse "${DESTINATION_VMDK}" 2>&1 | tee "${REDIRECT}" + elif [ ${RESTORE_DISK_FORMAT} -eq 3 ]; then + ${VMKFSTOOLS_CMD} -i "${SOURCE_VMDK}" -a "${ADAPTER_FORMAT}" -d thin "${DESTINATION_VMDK}" 2>&1 | tee "${REDIRECT}" + elif [ ${RESTORE_DISK_FORMAT} -eq 4 ]; then + if [[ "${VER}" == "4" ]] || [[ "${VER}" == "5" ]]; then + ${VMKFSTOOLS_CMD} -i "${SOURCE_VMDK}" -a "${ADAPTER_FORMAT}" -d eagerzeroedthick "${DESTINATION_VMDK}" 2>&1 | tee "${REDIRECT}" + else + ${VMKFSTOOLS_CMD} -i "${SOURCE_VMDK}" -a "${ADAPTER_FORMAT}" "${DESTINATION_VMDK}" 2>&1 | tee "${REDIRECT}" + fi + fi + else + logger "\nSOURCE: \"${SOURCE_VMDK}\"" + logger "\tORIGINAL_VMX_LINE: -->${ORIGINAL_VMX_LINE}<--" + logger "DESTINATION: \"${DESTINATION_VMDK}\"" + logger "\tMODIFIED_VMX_LINE: -->${MODIFIED_VMX_LINE}<--" + fi + done + unset IFS + IFS="${OLD_IFS}" + + #register VM on ESX(i) host + logger "Registering $VM_DISPLAY_NAME ..." + + if [ ! "${DEVEL_MODE}" == "2" ]; then + ${VMWARE_CMD} solo/registervm "${VM_RESTORE_DIR}/${VM_VMX}" + fi + + logger "End time: $(date)" + logger "################## Completed restore for $VM_DISPLAY_NAME! #####################\n" + fi + fi +fi + VMDK_LIST_TO_MODIFY='' + done + unset IFS + + endTimer +} + +#################### +# # +# Start of Script # +# # +#################### + +IS_4I=0 + +if [ ! -f /bin/bash ]; then + IS_4I=1 +fi + +#read user input +while getopts ":c:l:d:" ARGS; do + case $ARGS in + c) CONFIG_FILE="${OPTARG}" + ;; + l) + LOG_OUTPUT="${OPTARG}" + ;; + d) + DEVEL_MODE="${OPTARG}" + ;; + :) + echo "Option -${OPTARG} requires an argument." + exit 1 + ;; + *) + usage + exit 1 + ;; + esac +done + +#performs a check on the number of commandline arguments + verifies $2 is a valid file +sanityCheck $# + +ghettoVCBrestore ${CONFIG_FILE} diff --git a/ghettoVCB-restore_vm_restore_configuration_template b/ghettoVCB-restore_vm_restore_configuration_template new file mode 100644 index 0000000..0b3c840 --- /dev/null +++ b/ghettoVCB-restore_vm_restore_configuration_template @@ -0,0 +1,8 @@ +#";;" +# DISK_FORMATS +# 1 = zeroedthick +# 2 = 2gbsparse +# 3 = thin +# 4 = eagerzeroedthick +# e.g. +# "/vmfs/volumes/dlgCore-NFS-bigboi.VM-Backups/WILLIAM_BACKUPS/STA202I/STA202I-2009-08-18--1;/vmfs/volumes/himalaya-local-SATA.RE4-GP:Storage;1" diff --git a/ghettoVCB-vm_backup_configuration_template b/ghettoVCB-vm_backup_configuration_template index 344fc76..e898066 100644 --- a/ghettoVCB-vm_backup_configuration_template +++ b/ghettoVCB-vm_backup_configuration_template @@ -7,7 +7,6 @@ ITER_TO_WAIT_SHUTDOWN=4 POWER_DOWN_TIMEOUT=5 SNAPSHOT_TIMEOUT=15 ENABLE_COMPRESSION=0 -ADAPTER_FORMAT=buslogic VM_SNAPSHOT_MEMORY=0 VM_SNAPSHOT_QUIESCE=0 VMDK_FILES_TO_BACKUP="VCAP.vmdk,VCAP_2.vmdk" diff --git a/ghettoVCB.conf b/ghettoVCB.conf index 1e71759..3d548c7 100644 --- a/ghettoVCB.conf +++ b/ghettoVCB.conf @@ -6,7 +6,6 @@ ENABLE_HARD_POWER_OFF=0 ITER_TO_WAIT_SHUTDOWN=3 POWER_DOWN_TIMEOUT=5 ENABLE_COMPRESSION=0 -ADAPTER_FORMAT=buslogic VM_SNAPSHOT_MEMORY=0 VM_SNAPSHOT_QUIESCE=0 ENABLE_NON_PERSISTENT_NFS=0 @@ -20,5 +19,6 @@ EMAIL_LOG=0 EMAIL_DEBUG=0 EMAIL_SERVER=auroa.primp-industries.com EMAIL_SERVER_PORT=25 +EMAIL_DELAY_INTERVAL=1 EMAIL_TO=auroa@primp-industries.com EMAIL_FROM=root@ghettoVCB diff --git a/ghettoVCB.sh b/ghettoVCB.sh index c7e1a1e..78429aa 100755 --- a/ghettoVCB.sh +++ b/ghettoVCB.sh @@ -41,9 +41,6 @@ ENABLE_COMPRESSION=0 ####### NEW PARAMS ######### ############################ -# Disk adapter type: buslogic, lsilogic or ide -ADAPTER_FORMAT=buslogic - # Include VMs memory when taking snapshot VM_SNAPSHOT_MEMORY=0 @@ -82,6 +79,9 @@ EMAIL_DEBUG=0 # Email log 1=yes, 0=no EMAIL_LOG=0 +# Email Delay Interval from NC (netcat) - default 1 +EMAIL_DELAY_INTERVAL=1 + # Email SMTP server EMAIL_SERVER=auroa.primp-industries.com @@ -104,7 +104,7 @@ VMDK_FILES_TO_BACKUP="all" # default 15min timeout SNAPSHOT_TIMEOUT=15 -LAST_MODIFIED_DATE=2011_06_28 +LAST_MODIFIED_DATE=2011_11_19 VERSION=1 VERSION_STRING=${LAST_MODIFIED_DATE}_${VERSION} @@ -117,6 +117,7 @@ printUsage() { echo "# ghettoVCB for ESX/ESXi 3.5, 4.x+ and 5.0" echo "# Author: William Lam" echo "# http://www.virtuallyghetto.com/" + echo "# Documentation: http://communities.vmware.com/docs/DOC-8760" echo "# Created: 11/17/2008" echo "# Last modified: ${LAST_MODIFIED_DATE} Version ${VERSION}" echo "#" @@ -227,18 +228,26 @@ sanityCheck() { fi ESX_VERSION=$(vmware -v | awk '{print $3}') - if [[ "${ESX_VERSION}" == "4.0.0" ]] || [[ "${ESX_VERSION}" == "4.1.0" ]] || [[ "${ESX_VERSION}" == "5.0.0" ]]; then + if [[ "${ESX_VERSION}" == "5.0.0" ]]; then + VER=5 + elif [[ "${ESX_VERSION}" == "4.0.0" ]] || [[ "${ESX_VERSION}" == "4.1.0" ]]; then VER=4 else ESX_VERSION=$(vmware -v | awk '{print $4}') if [[ "${ESX_VERSION}" == "3.5.0" ]] || [[ "${ESX_VERSION}" == "3i" ]]; then VER=3 else - echo "You're not running ESX(i) 3.5+ or 4.x+!" + echo "You're not running ESX(i) 3.5, 4.x, 5.x!" exit 1 fi fi + NEW_VIMCMD_SNAPSHOT="no" + ${VMWARE_CMD} vmsvc/snapshot.remove | grep "snapshotId" > /dev/null 2>&1 + if [ $? -eq 0 ]; then + NEW_VIMCMD_SNAPSHOT="yes" + fi + if [[ "${EMAIL_LOG}" -eq 1 ]] && [[ -f /usr/bin/nc ]] || [[ -f /bin/nc ]]; then if [ -f /usr/bin/nc ]; then NC_BIN=/usr/bin/nc @@ -284,7 +293,6 @@ captureDefaultConfigurations() { DEFAULT_POWER_DOWN_TIMEOUT="${POWER_DOWN_TIMEOUT}" DEFAULT_SNAPSHOT_TIMEOUT="${SNAPSHOT_TIMEOUT}" DEFAULT_ENABLE_COMPRESSION="${ENABLE_COMPRESSION}" - DEFAULT_ADAPTER_FORMAT="${ADAPTER_FORMAT}" DEFAULT_VM_SNAPSHOT_MEMORY="${VM_SNAPSHOT_MEMORY}" DEFAULT_VM_SNAPSHOT_QUIESCE="${VM_SNAPSHOT_QUIESCE}" DEFAULT_VMDK_FILES_TO_BACKUP="${VMDK_FILES_TO_BACKUP}" @@ -302,7 +310,6 @@ useDefaultConfigurations() { POWER_DOWN_TIMEOUT="${DEFAULT_POWER_DOWN_TIMEOUT}" SNAPSHOT_TIMEOUT="${DEFAULT_SNAPSHOT_TIMEOUT}" ENABLE_COMPRESSION="${DEFAULT_ENABLE_COMPRESSION}" - ADAPTER_FORMAT="${DEFAULT_ADAPTER_FORMAT}" VM_SNAPSHOT_MEMORY="${DEFAULT_VM_SNAPSHOT_MEMORY}" VM_SNAPSHOT_QUIESCE="${DEFAULT_VM_SNAPSHOT_QUIESCE}" VMDK_FILES_TO_BACKUP="all" @@ -440,7 +447,6 @@ dumpVMConfigurations() { logger "info" "CONFIG - VM_BACKUP_ROTATION_COUNT = ${VM_BACKUP_ROTATION_COUNT}" logger "info" "CONFIG - VM_BACKUP_DIR_NAMING_CONVENTION = ${VM_BACKUP_DIR_NAMING_CONVENTION}" logger "info" "CONFIG - DISK_BACKUP_FORMAT = ${DISK_BACKUP_FORMAT}" - logger "info" "CONFIG - ADAPTER_FORMAT = ${ADAPTER_FORMAT}" logger "info" "CONFIG - POWER_VM_DOWN_BEFORE_BACKUP = ${POWER_VM_DOWN_BEFORE_BACKUP}" logger "info" "CONFIG - ENABLE_HARD_POWER_OFF = ${ENABLE_HARD_POWER_OFF}" logger "info" "CONFIG - ITER_TO_WAIT_SHUTDOWN = ${ITER_TO_WAIT_SHUTDOWN}" @@ -456,6 +462,7 @@ dumpVMConfigurations() { logger "info" "CONFIG - EMAIL_DEBUG = ${EMAIL_DEBUG}" logger "info" "CONFIG - EMAIL_SERVER = ${EMAIL_SERVER}" logger "info" "CONFIG - EMAIL_SERVER_PORT = ${EMAIL_SERVER_PORT}" + logger "info" "CONFIG - EMAIL_DELAY_INTERVAL = ${EMAIL_DELAY_INTERVAL}" logger "info" "CONFIG - EMAIL_FROM = ${EMAIL_FROM}" logger "info" "CONFIG - EMAIL_TO = ${EMAIL_TO}" fi @@ -863,7 +870,7 @@ ghettoVCB() { if [ $? -eq 1 ]; then FORMAT_OPTION="UNKNOWN" if [ "${DISK_BACKUP_FORMAT}" == "zeroedthick" ]; then - if [ "${VER}" == "4" ]; then + if [[ "${VER}" == "4" ]] || [[ "${VER}" == "5" ]] ; then FORMAT_OPTION="zeroedthick" else FORMAT_OPTION="" @@ -873,7 +880,7 @@ ghettoVCB() { elif [ "${DISK_BACKUP_FORMAT}" == "thin" ]; then FORMAT_OPTION="thin" elif [ "${DISK_BACKUP_FORMAT}" == "eagerzeroedthick" ]; then - if [ "${VER}" == "4" ]; then + if [[ "${VER}" == "4" ]] || [[ "${VER}" == "5" ]] ; then FORMAT_OPTION="eagerzeroedthick" else FORMAT_OPTION="" @@ -888,6 +895,8 @@ ghettoVCB() { tail -f "${VMDK_OUTPUT}" & TAIL_PID=$! + ADAPTER_FORMAT=$(grep -i "ddb.adapterType" "${SOURCE_VMDK}" | awk -F "=" '{print $2}' | sed -e 's/^[[:blank:]]*//;s/[[:blank:]]*$//;s/"//g') + if [ -z "${FORMAT_OPTION}" ] ; then logger "debug" "${VMKFSTOOLS_CMD} -i \"${SOURCE_VMDK}\" -a \"${ADAPTER_FORMAT}\" \"${DESTINATION_VMDK}\"" ${VMKFSTOOLS_CMD} -i "${SOURCE_VMDK}" -a "${ADAPTER_FORMAT}" "${DESTINATION_VMDK}" > "${VMDK_OUTPUT}" 2>&1 @@ -919,7 +928,12 @@ ghettoVCB() { #powered on VMs only w/snapshots if [[ ${SNAP_SUCCESS} -eq 1 ]] && [[ ! ${POWER_VM_DOWN_BEFORE_BACKUP} -eq 1 ]] && [[ "${ORGINAL_VM_POWER_STATE}" == "Powered on" ]] || [[ "${ORGINAL_VM_POWER_STATE}" == "Suspended" ]]; then - ${VMWARE_CMD} vmsvc/snapshot.remove ${VM_ID} > /dev/null 2>&1 + if [ "${NEW_VIMCMD_SNAPSHOT}" == "yes" ]; then + SNAPSHOT_ID=$(${VMWARE_CMD} vmsvc/snapshot.get 16 | grep -E '(Snapshot Name|Snapshot Id)' | grep -A1 ${SNAPSHOT_NAME} | grep "Snapshot Id" | awk -F ":" '{print $2}' | sed -e 's/^[[:blank:]]*//;s/[[:blank:]]*$//') + ${VMWARE_CMD} vmsvc/snapshot.remove ${VM_ID} ${SNAPSHOT_ID} > /dev/null 2>&1 + else + ${VMWARE_CMD} vmsvc/snapshot.remove ${VM_ID} > /dev/null 2>&1 + fi #do not continue until all snapshots have been committed logger "info" "Removing snapshot from ${VM_NAME} ..." @@ -977,6 +991,22 @@ ghettoVCB() { logger "info" "WARN: ${VM_NAME} has some Independent VMDKs that can not be backed up!\n"; [[ ${ENABLE_COMPRESSION} -eq 1 ]] && [[ $COMPRESSED_OK -eq 1 ]] || echo "WARN: ${VM_NAME} has some Independent VMDKs that can not be backed up" > ${VM_BACKUP_DIR}/STATUS.warn VMDK_FAILED=1 + #experimental + #create symlink for the very last backup to support rsync functionality for additinal replication + if [ "${RSYNC_LINK}" -eq 1 ]; then + SYMLINK_DST=${VM_BACKUP_DIR} + if [ ${ENABLE_COMPRESSION} -eq 1 ]; then + SYMLINK_DST1="${RSYNC_LINK_DIR}.gz" + else + SYMLINK_DST1=${RSYNC_LINK_DIR} + fi + SYMLINK_SRC="$(echo "${SYMLINK_DST%*-*-*-*_*-*-*}")-symlink" + logger "info" "Creating symlink \"${SYMLINK_SRC}\" to \"${SYMLINK_DST1}\"" + ln -sf "${SYMLINK_DST1}" "${SYMLINK_SRC}" + fi + + #storage info after backup + storageInfo "after" else logger "info" "Successfully completed backup for ${VM_NAME}!\n" [[ ${ENABLE_COMPRESSION} -eq 1 ]] && [[ $COMPRESSED_OK -eq 1 ]] || echo "Successfully completed backup" > ${VM_BACKUP_DIR}/STATUS.ok @@ -993,7 +1023,7 @@ ghettoVCB() { fi SYMLINK_SRC="$(echo "${SYMLINK_DST%*-*-*-*_*-*-*}")-symlink" logger "info" "Creating symlink \"${SYMLINK_SRC}\" to \"${SYMLINK_DST1}\"" - ln -s "${SYMLINK_DST1}" "${SYMLINK_SRC}" + ln -sf "${SYMLINK_DST1}" "${SYMLINK_SRC}" fi #storage info after backup @@ -1073,6 +1103,15 @@ buildHeaders() { sendMail() { #close email message if [ "${EMAIL_LOG}" -eq 1 ]; then + #validate firewall has email port open for ESXi 5 + if [ "${VER}" == "5" ]; then + /sbin/esxcli network firewall ruleset rule list | grep "${EMAIL_SERVER_PORT}" > /dev/null 2>&1 + if [ $? -eq 1 ]; then + logger "info" "ERROR: Please enable firewall rule for email traffic on port ${EMAIL_SERVER_PORT}\n" + logger "info" "Please refer to ghettoVCB documentation for ESXi 5 firewall configuration\n" + fi + fi + echo "${EMAIL_TO}" | grep "," > /dev/null 2>&1 if [ $? -eq 0 ]; then ORIG_IFS=${IFS} @@ -1080,7 +1119,7 @@ sendMail() { for i in ${EMAIL_TO}; do buildHeaders ${i} - "${NC_BIN}" "${EMAIL_SERVER}" "${EMAIL_SERVER_PORT}" < "${EMAIL_LOG_CONTENT}" > /dev/null 2>&1 + "${NC_BIN}" -i "${EMAIL_DELAY_INTERVAL}" "${EMAIL_SERVER}" "${EMAIL_SERVER_PORT}" < "${EMAIL_LOG_CONTENT}" > /dev/null 2>&1 if [ $? -eq 1 ]; then logger "info" "ERROR: Failed to email log output to ${EMAIL_SERVER}:${EMAIL_SERVER_PORT} to ${EMAIL_TO}\n" fi @@ -1088,7 +1127,7 @@ sendMail() { unset IFS else buildHeaders ${EMAIL_TO} - "${NC_BIN}" "${EMAIL_SERVER}" "${EMAIL_SERVER_PORT}" < "${EMAIL_LOG_CONTENT}" > /dev/null 2>&1 + "${NC_BIN}" -i "${EMAIL_DELAY_INTERVAL}" "${EMAIL_SERVER}" "${EMAIL_SERVER_PORT}" < "${EMAIL_LOG_CONTENT}" > /dev/null 2>&1 if [ $? -eq 1 ]; then logger "info" "ERROR: Failed to email log output to ${EMAIL_SERVER}:${EMAIL_SERVER_PORT} to ${EMAIL_TO}\n" fi