/ghettoVCB.sh

https://github.com/marcosmamorim/ghettoVCB · Shell · 1197 lines · 1042 code · 81 blank · 74 comment · 119 complexity · 47faf4cf81f7a24b9ff4008d5142679b MD5 · raw file

  1. # Author: William Lam
  2. # Created Date: 11/17/2008
  3. # http://www.virtuallyghetto.com/
  4. # http://communities.vmware.com/docs/DOC-8760
  5. ##################################################################
  6. # directory that all VM backups should go (e.g. /vmfs/volumes/SAN_LUN1/mybackupdir)
  7. VM_BACKUP_VOLUME=/vmfs/volumes/dlgCore-NFS-bigboi.VM-Backups/WILLIAM_BACKUPS
  8. # Format output of VMDK backup
  9. # zeroedthick
  10. # 2gbsparse
  11. # thin
  12. # eagerzeroedthick
  13. DISK_BACKUP_FORMAT=thin
  14. # Number of backups for a given VM before deleting
  15. VM_BACKUP_ROTATION_COUNT=3
  16. # Shutdown guestOS prior to running backups and power them back on afterwards
  17. # This feature assumes VMware Tools are installed, else they will not power down and loop forever
  18. # 1=on, 0 =off
  19. POWER_VM_DOWN_BEFORE_BACKUP=0
  20. # enable shutdown code 1=on, 0 = off
  21. ENABLE_HARD_POWER_OFF=0
  22. # if the above flag "ENABLE_HARD_POWER_OFF "is set to 1, then will look at this flag which is the # of iterations
  23. # the script will wait before executing a hard power off, this will be a multiple of 60seconds
  24. # (e.g) = 3, which means this will wait up to 180seconds (3min) before it just powers off the VM
  25. ITER_TO_WAIT_SHUTDOWN=3
  26. # Number of iterations the script will wait before giving up on powering down the VM and ignoring it for backup
  27. # this will be a multiple of 60 (e.g) = 5, which means this will wait up to 300secs (5min) before it gives up
  28. POWER_DOWN_TIMEOUT=5
  29. # enable compression with gzip+tar 1=on, 0=off
  30. ENABLE_COMPRESSION=0
  31. ############################
  32. ####### NEW PARAMS #########
  33. ############################
  34. # Disk adapter type: buslogic, lsilogic or ide
  35. ADAPTER_FORMAT=buslogic
  36. # Include VMs memory when taking snapshot
  37. VM_SNAPSHOT_MEMORY=0
  38. # Quiesce VM when taking snapshot (requires VMware Tools to be installed)
  39. VM_SNAPSHOT_QUIESCE=0
  40. ##########################################################
  41. # NON-PERSISTENT NFS-BACKUP ONLY
  42. #
  43. # ENABLE NON PERSISTENT NFS BACKUP 1=on, 0=off
  44. ENABLE_NON_PERSISTENT_NFS=0
  45. # umount NFS datastore after backup is complete 1=yes, 0=no
  46. UNMOUNT_NFS=0
  47. # IP Address of NFS Server
  48. NFS_SERVER=172.51.0.192
  49. # Path of exported folder residing on NFS Server (e.g. /some/mount/point )
  50. NFS_MOUNT=/upload
  51. # Non-persistent NFS datastore display name of choice
  52. NFS_LOCAL_NAME=backup
  53. # Name of backup directory for VMs residing on the NFS volume
  54. NFS_VM_BACKUP_DIR=mybackups
  55. ############################
  56. ######### EMAIL ############
  57. ############################
  58. # Email debug 1=yes, 0=no
  59. EMAIL_DEBUG=0
  60. # Email log 1=yes, 0=no
  61. EMAIL_LOG=0
  62. # Email SMTP server
  63. EMAIL_SERVER=auroa.primp-industries.com
  64. # Email SMTP server port
  65. EMAIL_SERVER_PORT=25
  66. # Email FROM
  67. EMAIL_FROM=root@ghettoVCB
  68. # Email RCPT
  69. EMAIL_TO=auroa@primp-industries.com
  70. ########################## DO NOT MODIFY PAST THIS LINE ##########################
  71. # RSYNC LINK 1=yes, 0 = no
  72. RSYNC_LINK=0
  73. LOG_LEVEL="info"
  74. VMDK_FILES_TO_BACKUP="all"
  75. # default 15min timeout
  76. SNAPSHOT_TIMEOUT=15
  77. LAST_MODIFIED_DATE=2011_06_28
  78. VERSION=1
  79. VERSION_STRING=${LAST_MODIFIED_DATE}_${VERSION}
  80. # Directory naming convention for backup rotations (please ensure there are no spaces!)
  81. VM_BACKUP_DIR_NAMING_CONVENTION="$(date +%F_%H-%M-%S)"
  82. printUsage() {
  83. echo "###############################################################################"
  84. echo "#"
  85. echo "# ghettoVCB for ESX/ESXi 3.5, 4.x+ and 5.0"
  86. echo "# Author: William Lam"
  87. echo "# http://www.virtuallyghetto.com/"
  88. echo "# Created: 11/17/2008"
  89. echo "# Last modified: ${LAST_MODIFIED_DATE} Version ${VERSION}"
  90. echo "#"
  91. echo "###############################################################################"
  92. echo
  93. echo "Usage: $0 -f [VM_BACKUP_UP_LIST] -c [VM_CONFIG_DIR] -l [LOG_FILE] -d [DEBUG_LEVEL] -g [GLOBAL_CONF] -e [VM_EXCLUSION_LIST]"
  94. echo
  95. echo "OPTIONS:"
  96. echo " -a Backup all VMs on host"
  97. echo " -f List of VMs to backup"
  98. echo " -c VM configuration directory for VM backups"
  99. echo " -g Path to global ghettoVCB configuration file"
  100. echo " -l File to output logging"
  101. echo " -d Debug level [info|debug|dryrun] (default: info)"
  102. echo
  103. echo "(e.g.)"
  104. echo -e "\nBackup VMs stored in a list"
  105. echo -e "\t$0 -f vms_to_backup"
  106. echo -e "\nBackup all VMs residing on this host"
  107. echo -e "\t$0 -a"
  108. echo -e "\nBackup all VMs residing on this host except for the VMs in the exclusion list"
  109. echo -e "\t$0 -a -e vm_exclusion_list"
  110. echo -e "\nBackup VMs based on specific configuration located in directory"
  111. echo -e "\t$0 -f vms_to_backup -c vm_backup_configs"
  112. echo -e "\nBackup VMs using global ghettoVCB configuration file"
  113. echo -e "\t$0 -f vms_to_backup -g /global/ghettoVCB.conf"
  114. echo -e "\nOutput will log to /tmp/ghettoVCB.log (consider logging to local or remote datastore to persist logs)"
  115. echo -e "\t$0 -f vms_to_backup -l /vmfs/volume/local-storage/ghettoVCB.log"
  116. echo -e "\nDry run (no backup will take place)"
  117. echo -e "\t$0 -f vms_to_backup -d dryrun"
  118. echo
  119. exit 1
  120. }
  121. logger() {
  122. LOG_TYPE=$1
  123. MSG=$2
  124. if [[ "${LOG_LEVEL}" == "debug" ]] && [[ "${LOG_TYPE}" == "debug" ]] || [[ "${LOG_TYPE}" == "info" ]] || [[ "${LOG_TYPE}" == "dryrun" ]]; then
  125. TIME=$(date +%F" "%H:%M:%S)
  126. if [ "${LOG_TO_STDOUT}" -eq 1 ]; then
  127. echo -e "${TIME} -- ${LOG_TYPE}: ${MSG}"
  128. fi
  129. if [ -n "${LOG_OUTPUT}" ]; then
  130. echo -e "${TIME} -- ${LOG_TYPE}: ${MSG}" >> "${LOG_OUTPUT}"
  131. fi
  132. if [ "${EMAIL_LOG}" -eq 1 ]; then
  133. echo -ne "${TIME} -- ${LOG_TYPE}: ${MSG}\r\n" >> "${EMAIL_LOG_OUTPUT}"
  134. fi
  135. fi
  136. }
  137. sanityCheck() {
  138. NUM_OF_ARGS=$1
  139. if [ "${USE_GLOBAL_CONF}" -eq 1 ]; then
  140. reConfigureGhettoVCBConfiguration "${GLOBAL_CONF}"
  141. fi
  142. #always log to STDOUT, use "> /dev/null" to ignore output
  143. LOG_TO_STDOUT=1
  144. #if no logfile then provide default logfile in /tmp
  145. if [ -z "${LOG_OUTPUT}" ]; then
  146. LOG_OUTPUT="/tmp/ghettoVCB-$(date +%F_%H-%M-%S).log"
  147. echo "Logging output to \"${LOG_OUTPUT}\" ..."
  148. fi
  149. touch "${LOG_OUTPUT}"
  150. # REDIRECT is used by the "tail" trick, use REDIRECT=/dev/null to redirect vmkfstool to STDOUT only
  151. REDIRECT=${LOG_OUTPUT}
  152. if [[ ${NUM_OF_ARGS} -lt 1 ]] || [[ ${NUM_OF_ARGS} -gt 12 ]]; then
  153. logger "info" "ERROR: Incorrect number of arguments!"
  154. printUsage
  155. fi
  156. if [[ ! -f "${VM_FILE}" ]] && [[ "${USE_VM_CONF}" -eq 0 ]] && [[ "${BACKUP_ALL_VMS}" -eq 0 ]]; then
  157. logger "info" "ERROR: \"${VM_FILE}\" is not valid VM input file!"
  158. printUsage
  159. fi
  160. if [[ ! -f "${VM_EXCLUSION_FILE}" ]] && [[ "${EXCLUDE_SOME_VMS}" -eq 1 ]]; then
  161. logger "info" "ERROR: \"${VM_EXCLUSION_FILE}\" is not valid VM exclusion input file!"
  162. printUsage
  163. fi
  164. if [[ ! -d "${CONFIG_DIR}" ]] && [[ "${USE_VM_CONF}" -eq 1 ]]; then
  165. logger "info" "ERROR: \"${CONFIG_DIR}\" is not valid directory!"
  166. printUsage
  167. fi
  168. if [[ ! -f "${GLOBAL_CONF}" ]] && [[ "${USE_GLOBAL_CONF}" -eq 1 ]]; then
  169. logger "info" "ERROR: \"${GLOBAL_CONF}\" is not valid global configuration file!"
  170. printUsage
  171. fi
  172. if [ -f /usr/bin/vmware-vim-cmd ]; then
  173. VMWARE_CMD=/usr/bin/vmware-vim-cmd
  174. VMKFSTOOLS_CMD=/usr/sbin/vmkfstools
  175. elif [ -f /bin/vim-cmd ]; then
  176. VMWARE_CMD=/bin/vim-cmd
  177. VMKFSTOOLS_CMD=/sbin/vmkfstools
  178. else
  179. logger "info" "ERROR: Unable to locate *vimsh*! You're not running ESX(i) 3.5+, 4.x+ or 5.0!"
  180. echo "ERROR: Unable to locate *vimsh*! You're not running ESX(i) 3.5+, 4.x+ or 5.0!"
  181. exit 1
  182. fi
  183. ESX_VERSION=$(vmware -v | awk '{print $3}')
  184. if [[ "${ESX_VERSION}" == "4.0.0" ]] || [[ "${ESX_VERSION}" == "4.1.0" ]] || [[ "${ESX_VERSION}" == "5.0.0" ]]; then
  185. VER=4
  186. else
  187. ESX_VERSION=$(vmware -v | awk '{print $4}')
  188. if [[ "${ESX_VERSION}" == "3.5.0" ]] || [[ "${ESX_VERSION}" == "3i" ]]; then
  189. VER=3
  190. else
  191. echo "You're not running ESX(i) 3.5+ or 4.x+!"
  192. exit 1
  193. fi
  194. fi
  195. if [[ "${EMAIL_LOG}" -eq 1 ]] && [[ -f /usr/bin/nc ]] || [[ -f /bin/nc ]]; then
  196. if [ -f /usr/bin/nc ]; then
  197. NC_BIN=/usr/bin/nc
  198. elif [ -f /bin/nc ]; then
  199. NC_BIN=/bin/nc
  200. fi
  201. else
  202. EMAIL_LOG=0
  203. fi
  204. if [ ! $(whoami) == "root" ]; then
  205. logger "info" "This script needs to be executed by \"root\"!"
  206. echo "ERROR: This script needs to be executed by \"root\"!"
  207. exit 1
  208. fi
  209. }
  210. startTimer() {
  211. START_TIME=$(date)
  212. S_TIME=$(date +%s)
  213. }
  214. endTimer() {
  215. END_TIME=$(date)
  216. E_TIME=$(date +%s)
  217. DURATION=$(echo $((E_TIME - S_TIME)))
  218. #calculate overall completion time
  219. if [ ${DURATION} -le 60 ]; then
  220. logger "info" "Backup Duration: ${DURATION} Seconds"
  221. else
  222. logger "info" "Backup Duration: $(awk 'BEGIN{ printf "%.2f\n", '${DURATION}'/60}') Minutes"
  223. fi
  224. }
  225. captureDefaultConfigurations() {
  226. DEFAULT_VM_BACKUP_VOLUME="${VM_BACKUP_VOLUME}"
  227. DEFAULT_DISK_BACKUP_FORMAT="${DISK_BACKUP_FORMAT}"
  228. DEFAULT_VM_BACKUP_ROTATION_COUNT="${VM_BACKUP_ROTATION_COUNT}"
  229. DEFAULT_POWER_VM_DOWN_BEFORE_BACKUP="${POWER_VM_DOWN_BEFORE_BACKUP}"
  230. DEFAULT_ENABLE_HARD_POWER_OFF="${ENABLE_HARD_POWER_OFF}"
  231. DEFAULT_ITER_TO_WAIT_SHUTDOWN="${ITER_TO_WAIT_SHUTDOWN}"
  232. DEFAULT_POWER_DOWN_TIMEOUT="${POWER_DOWN_TIMEOUT}"
  233. DEFAULT_SNAPSHOT_TIMEOUT="${SNAPSHOT_TIMEOUT}"
  234. DEFAULT_ENABLE_COMPRESSION="${ENABLE_COMPRESSION}"
  235. DEFAULT_ADAPTER_FORMAT="${ADAPTER_FORMAT}"
  236. DEFAULT_VM_SNAPSHOT_MEMORY="${VM_SNAPSHOT_MEMORY}"
  237. DEFAULT_VM_SNAPSHOT_QUIESCE="${VM_SNAPSHOT_QUIESCE}"
  238. DEFAULT_VMDK_FILES_TO_BACKUP="${VMDK_FILES_TO_BACKUP}"
  239. DEFAULT_EMAIL_LOG="${EMAIL_LOG}"
  240. DEFAULT_EMAIL_DEBUG="${EMAIL_DEBUG}"
  241. }
  242. useDefaultConfigurations() {
  243. VM_BACKUP_VOLUME="${DEFAULT_VM_BACKUP_VOLUME}"
  244. DISK_BACKUP_FORMAT="${DEFAULT_DISK_BACKUP_FORMAT}"
  245. VM_BACKUP_ROTATION_COUNT="${DEFAULT_VM_BACKUP_ROTATION_COUNT}"
  246. POWER_VM_DOWN_BEFORE_BACKUP="${DEFAULT_POWER_VM_DOWN_BEFORE_BACKUP}"
  247. ENABLE_HARD_POWER_OFF="${DEFAULT_ENABLE_HARD_POWER_OFF}"
  248. ITER_TO_WAIT_SHUTDOWN="${DEFAULT_ITER_TO_WAIT_SHUTDOWN}"
  249. POWER_DOWN_TIMEOUT="${DEFAULT_POWER_DOWN_TIMEOUT}"
  250. SNAPSHOT_TIMEOUT="${DEFAULT_SNAPSHOT_TIMEOUT}"
  251. ENABLE_COMPRESSION="${DEFAULT_ENABLE_COMPRESSION}"
  252. ADAPTER_FORMAT="${DEFAULT_ADAPTER_FORMAT}"
  253. VM_SNAPSHOT_MEMORY="${DEFAULT_VM_SNAPSHOT_MEMORY}"
  254. VM_SNAPSHOT_QUIESCE="${DEFAULT_VM_SNAPSHOT_QUIESCE}"
  255. VMDK_FILES_TO_BACKUP="all"
  256. EMAIL_LOG=0
  257. EMAIL_DEBUG=0
  258. }
  259. reConfigureGhettoVCBConfiguration() {
  260. GLOBAL_CONF=$1
  261. if [ -f "${GLOBAL_CONF}" ]; then
  262. . "${GLOBAL_CONF}"
  263. else
  264. useDefaultConfigurations
  265. fi
  266. }
  267. reConfigureBackupParam() {
  268. VM=$1
  269. if [ -e "${CONFIG_DIR}/${VM}" ]; then
  270. logger "info" "CONFIG - USING CONFIGURATION FILE = ${CONFIG_DIR}/${VM}"
  271. . "${CONFIG_DIR}/${VM}"
  272. else
  273. useDefaultConfigurations
  274. fi
  275. }
  276. dumpHostInfo() {
  277. VERSION=$(vmware -v)
  278. logger "debug" "HOST VERSION: ${VERSION}"
  279. echo ${VERSION} | grep "Server 3i" > /dev/null 2>&1
  280. if [ $? -eq 1 ]; then
  281. logger "debug" "HOST LEVEL: $(vmware -l)"
  282. fi
  283. logger "debug" "HOSTNAME: $(hostname)\n"
  284. }
  285. findVMDK() {
  286. VMDK_TO_SEARCH_FOR=$1
  287. #if [ "${USE_VM_CONF}" -eq 1 ]; then
  288. logger "debug" "findVMDK() - Searching for VMDK: \"${VMDK_TO_SEARCH_FOR}\" to backup"
  289. OLD_IFS2="${IFS}"
  290. IFS=","
  291. for k in ${VMDK_FILES_TO_BACKUP}
  292. do
  293. VMDK_FILE=$(echo $k | sed -e 's/^[[:blank:]]*//;s/[[:blank:]]*$//')
  294. if [ "${VMDK_FILE}" == "${VMDK_TO_SEARCH_FOR}" ]; then
  295. logger "debug" "findVMDK() - Found VMDK! - \"${VMDK_TO_SEARCH_FOR}\" to backup"
  296. isVMDKFound=1
  297. fi
  298. done
  299. IFS="${OLD_IFS2}"
  300. #fi
  301. }
  302. getVMDKs() {
  303. #get all VMDKs listed in .vmx file
  304. VMDKS_FOUND=$(grep -iE '(scsi|ide)' "${VMX_PATH}" | grep -i fileName | awk -F " " '{print $1}')
  305. TMP_IFS=${IFS}
  306. IFS=${ORIG_IFS}
  307. #loop through each disk and verify that it's currently present and create array of valid VMDKS
  308. for DISK in ${VMDKS_FOUND};
  309. do
  310. #extract the SCSI ID and use it to check for valid vmdk disk
  311. SCSI_ID=$(echo ${DISK%%.*})
  312. grep -i "${SCSI_ID}.present" "${VMX_PATH}" | grep -i "true" > /dev/null 2>&1
  313. #if valid, then we use the vmdk file
  314. if [ $? -eq 0 ]; then
  315. #verify disk is not independent
  316. grep -i "${SCSI_ID}.mode" "${VMX_PATH}" | grep -i "independent" > /dev/null 2>&1
  317. if [ $? -eq 1 ]; then
  318. grep -i "${SCSI_ID}.deviceType" "${VMX_PATH}" | grep -i "scsi-hardDisk" > /dev/null 2>&1
  319. #if we find the device type is of scsi-disk, then proceed
  320. if [ $? -eq 0 ]; then
  321. DISK=$(grep -i ${SCSI_ID}.fileName "${VMX_PATH}" | awk -F "\"" '{print $2}')
  322. echo "${DISK}" | grep "\/vmfs\/volumes" > /dev/null 2>&1
  323. if [ $? -eq 0 ]; then
  324. DISK_SIZE_IN_SECTORS=$(cat "${DISK}" | grep "VMFS" | grep ".vmdk" | awk '{print $2}')
  325. else
  326. DISK_SIZE_IN_SECTORS=$(cat "${VMX_DIR}/${DISK}" | grep "VMFS" | grep ".vmdk" | awk '{print $2}')
  327. fi
  328. DISK_SIZE=$(echo "${DISK_SIZE_IN_SECTORS}" | awk '{printf "%.0f\n",$1*512/1024/1024/1024}')
  329. VMDKS="${DISK}###${DISK_SIZE}:${VMDKS}"
  330. TOTAL_VM_SIZE=$((TOTAL_VM_SIZE+DISK_SIZE))
  331. else
  332. #if the deviceType is NULL for IDE which it is, thanks for the inconsistency VMware
  333. #we'll do one more level of verification by checking to see if an ext. of .vmdk exists
  334. #since we can not rely on the deviceType showing "ide-hardDisk"
  335. grep -i ${SCSI_ID}.fileName "${VMX_PATH}" | grep -i ".vmdk" > /dev/null 2>&1
  336. if [ $? -eq 0 ]; then
  337. DISK=$(grep -i ${SCSI_ID}.fileName "${VMX_PATH}" | awk -F "\"" '{print $2}')
  338. echo "${DISK}" | grep "\/vmfs\/volumes" > /dev/null 2>&1
  339. if [ $? -eq 0 ]; then
  340. DISK_SIZE_IN_SECTORS=$(cat "${DISK}" | grep "VMFS" | grep ".vmdk" | awk '{print $2}')
  341. else
  342. DISK_SIZE_IN_SECTORS=$(cat "${VMX_DIR}/${DISK}" | grep "VMFS" | grep ".vmdk" | awk '{print $2}')
  343. fi
  344. DISK_SIZE=$(echo "${DISK_SIZE_IN_SECTORS}" | awk '{printf "%.0f\n",$1*512/1024/1024/1024}')
  345. VMDKS="${DISK}###${DISK_SIZE}:${VMDKS}"
  346. TOTAL_VM_SIZE=$((TOTAL_VM_SIZE_IN+DISK_SIZE))
  347. fi
  348. fi
  349. else
  350. #independent disks are not affected by snapshots, hence they can not be backed up
  351. DISK=$(grep -i ${SCSI_ID}.fileName "${VMX_PATH}" | awk -F "\"" '{print $2}')
  352. echo "${DISK}" | grep "\/vmfs\/volumes" > /dev/null 2>&1
  353. if [ $? -eq 0 ]; then
  354. DISK_SIZE_IN_SECTORS=$(cat "${DISK}" | grep "VMFS" | grep ".vmdk" | awk '{print $2}')
  355. else
  356. DISK_SIZE_IN_SECTORS=$(cat "${VMX_DIR}/${DISK}" | grep "VMFS" | grep ".vmdk" | awk '{print $2}')
  357. fi
  358. DISK_SIZE=$(echo "${DISK_SIZE_IN_SECTORS}" | awk '{printf "%.0f\n",$1*512/1024/1024/1024}')
  359. INDEP_VMDKS="${DISK}###${DISK_SIZE}:${INDEP_VMDKS}"
  360. fi
  361. fi
  362. done
  363. IFS=${TMP_IFS}
  364. logger "debug" "getVMDKs() - ${VMDKS}"
  365. }
  366. dumpVMConfigurations() {
  367. logger "info" "CONFIG - VERSION = ${VERSION_STRING}"
  368. logger "info" "CONFIG - GHETTOVCB_PID = ${GHETTOVCB_PID}"
  369. logger "info" "CONFIG - VM_BACKUP_VOLUME = ${VM_BACKUP_VOLUME}"
  370. if [ "${ENABLE_NON_PERSISTENT_NFS}" -eq 1 ]; then
  371. logger "info" "CONFIG - ENABLE_NON_PERSISTENT_NFS = ${ENABLE_NON_PERSISTENT_NFS}"
  372. logger "info" "CONFIG - UNMOUNT_NFS = ${UNMOUNT_NFS}"
  373. logger "info" "CONFIG - NFS_SERVER = ${NFS_SERVER}"
  374. logger "info" "CONFIG - NFS_MOUNT = ${NFS_MOUNT}"
  375. fi
  376. logger "info" "CONFIG - VM_BACKUP_ROTATION_COUNT = ${VM_BACKUP_ROTATION_COUNT}"
  377. logger "info" "CONFIG - VM_BACKUP_DIR_NAMING_CONVENTION = ${VM_BACKUP_DIR_NAMING_CONVENTION}"
  378. logger "info" "CONFIG - DISK_BACKUP_FORMAT = ${DISK_BACKUP_FORMAT}"
  379. logger "info" "CONFIG - ADAPTER_FORMAT = ${ADAPTER_FORMAT}"
  380. logger "info" "CONFIG - POWER_VM_DOWN_BEFORE_BACKUP = ${POWER_VM_DOWN_BEFORE_BACKUP}"
  381. logger "info" "CONFIG - ENABLE_HARD_POWER_OFF = ${ENABLE_HARD_POWER_OFF}"
  382. logger "info" "CONFIG - ITER_TO_WAIT_SHUTDOWN = ${ITER_TO_WAIT_SHUTDOWN}"
  383. logger "info" "CONFIG - POWER_DOWN_TIMEOUT = ${POWER_DOWN_TIMEOUT}"
  384. logger "info" "CONFIG - SNAPSHOT_TIMEOUT = ${SNAPSHOT_TIMEOUT}"
  385. logger "info" "CONFIG - LOG_LEVEL = ${LOG_LEVEL}"
  386. logger "info" "CONFIG - BACKUP_LOG_OUTPUT = ${LOG_OUTPUT}"
  387. logger "info" "CONFIG - VM_SNAPSHOT_MEMORY = ${VM_SNAPSHOT_MEMORY}"
  388. logger "info" "CONFIG - VM_SNAPSHOT_QUIESCE = ${VM_SNAPSHOT_QUIESCE}"
  389. logger "info" "CONFIG - VMDK_FILES_TO_BACKUP = ${VMDK_FILES_TO_BACKUP}"
  390. logger "info" "CONFIG - EMAIL_LOG = ${EMAIL_LOG}"
  391. if [ "${EMAIL_LOG}" -eq 1 ]; then
  392. logger "info" "CONFIG - EMAIL_DEBUG = ${EMAIL_DEBUG}"
  393. logger "info" "CONFIG - EMAIL_SERVER = ${EMAIL_SERVER}"
  394. logger "info" "CONFIG - EMAIL_SERVER_PORT = ${EMAIL_SERVER_PORT}"
  395. logger "info" "CONFIG - EMAIL_FROM = ${EMAIL_FROM}"
  396. logger "info" "CONFIG - EMAIL_TO = ${EMAIL_TO}"
  397. fi
  398. logger "info" ""
  399. }
  400. checkVMBackupRotation() {
  401. local BACKUP_DIR_PATH=$1
  402. local VM_TO_SEARCH_FOR=$2
  403. #default rotation if variable is not defined
  404. if [ -z ${VM_BACKUP_ROTATION_COUNT} ]; then
  405. VM_BACKUP_ROTATION_COUNT=1
  406. fi
  407. LIST_BACKUPS=$(ls -t "${BACKUP_DIR_PATH}" | grep "${VM_TO_SEARCH_FOR}-[0-9]\{4\}-[0-9]\{2\}-[0-9]\{2\}_[0-9]\{2\}-[0-9]\{2\}-[0-9]\{2\}")
  408. BACKUPS_TO_KEEP=$(ls -t "${BACKUP_DIR_PATH}" | grep "${VM_TO_SEARCH_FOR}-[0-9]\{4\}-[0-9]\{2\}-[0-9]\{2\}_[0-9]\{2\}-[0-9]\{2\}-[0-9]\{2\}" | head -"${VM_BACKUP_ROTATION_COUNT}")
  409. ORIG_IFS=${IFS}
  410. IFS='
  411. '
  412. for i in ${LIST_BACKUPS};
  413. do
  414. FOUND=0
  415. for j in ${BACKUPS_TO_KEEP};
  416. do
  417. if [ $i == $j ]; then
  418. FOUND=1
  419. fi
  420. done
  421. if [ $FOUND -eq 0 ]; then
  422. logger "debug" "Removing $BACKUP_DIR_PATH/$i"
  423. rm -rf "$BACKUP_DIR_PATH/$i"
  424. #NFS I/O error handling hack
  425. if [ $? -ne 0 ]; then
  426. NFS_IO_HACK_COUNTER=0
  427. NFS_IO_HACK_STATUS=0
  428. NFS_IO_HACK_FILECHECK="$BACKUP_DIR_PATH/nfs_io.check"
  429. while [ "${NFS_IO_HACK_STATUS}" -eq 0 -a "${NFS_IO_HACK_COUNTER}" -lt 60 ];
  430. do
  431. sleep 1
  432. NFS_IO_HACK_COUNTER=$((NFS_IO_HACK_COUNTER+1))
  433. touch "${NFS_IO_HACK_FILECHECK}"
  434. if [ $? -eq 0 ]; then
  435. NFS_IO_HACK_STATUS=1
  436. fi
  437. done
  438. rm -rf "${NFS_IO_HACK_FILECHECK}"
  439. if [ "${NFS_IO_HACK_STATUS}" -eq 1 ]; then
  440. logger "info" "Slept ${NFS_IO_HACK_COUNTER} seconds to work around NFS I/O error"
  441. else
  442. logger "info" "Slept ${NFS_IO_HACK_COUNTER} seconds but failed work around for NFS I/O error"
  443. fi
  444. fi
  445. fi
  446. done
  447. IFS=${ORIG_IFS}
  448. }
  449. storageInfo() {
  450. SECTION=$1
  451. #SOURCE DATASTORE
  452. SRC_DATASTORE_CAPACITY=$($VMWARE_CMD hostsvc/datastore/info "${VMFS_VOLUME}" | grep -i "capacity" | awk '{print $3}' | sed 's/,//g')
  453. SRC_DATASTORE_FREE=$($VMWARE_CMD hostsvc/datastore/info "${VMFS_VOLUME}" | grep -i "freespace" | awk '{print $3}' | sed 's/,//g')
  454. SRC_DATASTORE_BLOCKSIZE=$($VMWARE_CMD hostsvc/datastore/info "${VMFS_VOLUME}" | grep -i blockSizeMb | awk '{print $3}' | sed 's/,//g')
  455. if [ -z ${SRC_DATASTORE_BLOCKSIZE} ]; then
  456. SRC_DATASTORE_BLOCKSIZE="NA"
  457. SRC_DATASTORE_MAX_FILE_SIZE="NA"
  458. else
  459. case ${SRC_DATASTORE_BLOCKSIZE} in
  460. 1)SRC_DATASTORE_MAX_FILE_SIZE="256 GB";;
  461. 2)SRC_DATASTORE_MAX_FILE_SIZE="512 GB";;
  462. 4)SRC_DATASTORE_MAX_FILE_SIZE="1024 GB";;
  463. 8)SRC_DATASTORE_MAX_FILE_SIZE="2048 GB";;
  464. esac
  465. fi
  466. SRC_DATASTORE_CAPACITY_GB=$(echo "${SRC_DATASTORE_CAPACITY}" | awk '{printf "%.1f\n",$1/1024/1024/1024}')
  467. SRC_DATASTORE_FREE_GB=$(echo "${SRC_DATASTORE_FREE}" | awk '{printf "%.1f\n",$1/1024/1024/1024}')
  468. #DESTINATION DATASTORE
  469. DST_VOL_1=$(echo "${VM_BACKUP_VOLUME#/*/*/}")
  470. DST_DATASTORE=$(echo "${DST_VOL_1%%/*}")
  471. DST_DATASTORE_CAPACITY=$($VMWARE_CMD hostsvc/datastore/info "${DST_DATASTORE}" | grep -i "capacity" | awk '{print $3}' | sed 's/,//g')
  472. DST_DATASTORE_FREE=$($VMWARE_CMD hostsvc/datastore/info "${DST_DATASTORE}" | grep -i "freespace" | awk '{print $3}' | sed 's/,//g')
  473. DST_DATASTORE_BLOCKSIZE=$($VMWARE_CMD hostsvc/datastore/info "${DST_DATASTORE}" | grep -i blockSizeMb | awk '{print $3}' | sed 's/,//g')
  474. if [ -z ${DST_DATASTORE_BLOCKSIZE} ]; then
  475. DST_DATASTORE_BLOCKSIZE="NA"
  476. DST_DATASTORE_MAX_FILE_SIZE="NA"
  477. else
  478. case ${DST_DATASTORE_BLOCKSIZE} in
  479. 1)DST_DATASTORE_MAX_FILE_SIZE="256 GB";;
  480. 2)DST_DATASTORE_MAX_FILE_SIZE="512 GB";;
  481. 4)DST_DATASTORE_MAX_FILE_SIZE="1024 GB";;
  482. 8)DST_DATASTORE_MAX_FILE_SIZE="2048 GB";;
  483. esac
  484. fi
  485. DST_DATASTORE_CAPACITY_GB=$(echo "${DST_DATASTORE_CAPACITY}" | awk '{printf "%.1f\n",$1/1024/1024/1024}')
  486. DST_DATASTORE_FREE_GB=$(echo "${DST_DATASTORE_FREE}" | awk '{printf "%.1f\n",$1/1024/1024/1024}')
  487. logger "debug" "Storage Information ${SECTION} backup: "
  488. logger "debug" "SRC_DATASTORE: ${VMFS_VOLUME}"
  489. logger "debug" "SRC_DATASTORE_CAPACITY: ${SRC_DATASTORE_CAPACITY_GB} GB"
  490. logger "debug" "SRC_DATASTORE_FREE: ${SRC_DATASTORE_FREE_GB} GB"
  491. logger "debug" "SRC_DATASTORE_BLOCKSIZE: ${SRC_DATASTORE_BLOCKSIZE}"
  492. logger "debug" "SRC_DATASTORE_MAX_FILE_SIZE: ${SRC_DATASTORE_MAX_FILE_SIZE}"
  493. logger "debug" ""
  494. logger "debug" "DST_DATASTORE: ${DST_DATASTORE}"
  495. logger "debug" "DST_DATASTORE_CAPACITY: ${DST_DATASTORE_CAPACITY_GB} GB"
  496. logger "debug" "DST_DATASTORE_FREE: ${DST_DATASTORE_FREE_GB} GB"
  497. logger "debug" "DST_DATASTORE_BLOCKSIZE: ${DST_DATASTORE_BLOCKSIZE}"
  498. logger "debug" "DST_DATASTORE_MAX_FILE_SIZE: ${DST_DATASTORE_MAX_FILE_SIZE}"
  499. if [[ "${SRC_DATASTORE_BLOCKSIZE}" != "NA" ]] && [[ "${DST_DATASTORE_BLOCKSIZE}" != "NA" ]]; then
  500. if [ "${SRC_DATASTORE_BLOCKSIZE}" -lt "${DST_DATASTORE_BLOCKSIZE}" ]; then
  501. logger "debug" ""
  502. logger "debug" "SRC VMFS blocksze of ${SRC_DATASTORE_BLOCKSIZE}MB is less than DST VMFS blocksize of ${DST_DATASTORE_BLOCKSIZE}MB which can be an issue for VM snapshots"
  503. fi
  504. fi
  505. logger "debug" ""
  506. }
  507. ghettoVCB() {
  508. VM_INPUT=$1
  509. VM_OK=0
  510. VM_FAILED=0
  511. VMDK_FAILED=0
  512. dumpHostInfo
  513. if [ ${ENABLE_NON_PERSISTENT_NFS} -eq 1 ]; then
  514. VM_BACKUP_VOLUME="/vmfs/volumes/${NFS_LOCAL_NAME}/${NFS_VM_BACKUP_DIR}"
  515. if [ "${LOG_LEVEL}" != "dryrun" ]; then
  516. #1 = readonly
  517. #0 = readwrite
  518. logger "debug" "Mounting NFS: ${NFS_SERVER}:${NFS_MOUNT} to /vmfs/volume/${NFS_LOCAL_NAME}"
  519. ${VMWARE_CMD} hostsvc/datastore/nas_create "${NFS_LOCAL_NAME}" "${NFS_SERVER}" "${NFS_MOUNT}" 0
  520. fi
  521. fi
  522. captureDefaultConfigurations
  523. if [ "${USE_GLOBAL_CONF}" -eq 1 ]; then
  524. logger "info" "CONFIG - USING GLOBAL GHETTOVCB CONFIGURATION FILE = ${GLOBAL_CONF}"
  525. fi
  526. if [ "${USE_VM_CONF}" -eq 0 ]; then
  527. dumpVMConfigurations
  528. fi
  529. #dump out all virtual machines allowing for spaces now
  530. ${VMWARE_CMD} vmsvc/getallvms | sed 's/[[:blank:]]\{3,\}/ /g' | awk -F' ' '{print "\""$1"\";\""$2"\";\""$3"\""}' | sed 's/\] /\]\";\"/g' | sed '1,1d' > /tmp/vms_list
  531. if [ "${BACKUP_ALL_VMS}" -eq 1 ]; then
  532. ${VMWARE_CMD} vmsvc/getallvms | sed 's/[[:blank:]]\{3,\}/ /g' | awk -F' ' '{print ""$2""}' | sed '1,1d' | sed '/^$/d' > "${VM_INPUT}"
  533. fi
  534. ORIG_IFS=${IFS}
  535. IFS='
  536. '
  537. for VM_NAME in $(cat "${VM_INPUT}" | grep -v "#" | sed '/^$/d' | sed -e 's/^[[:blank:]]*//;s/[[:blank:]]*$//');
  538. do
  539. IGNORE_VM=0
  540. if [ "${EXCLUDE_SOME_VMS}" -eq 1 ]; then
  541. grep -E "${VM_NAME}" "${VM_EXCLUSION_FILE}" > /dev/null 2>&1
  542. if [ $? -eq 0 ]; then
  543. IGNORE_VM=1
  544. fi
  545. fi
  546. VM_ID=$(grep -E "\"${VM_NAME}\"" /tmp/vms_list | awk -F ";" '{print $1}' | sed 's/"//g')
  547. #ensure default value if one is not selected or variable is null
  548. if [ -z ${VM_BACKUP_DIR_NAMING_CONVENTION} ]; then
  549. VM_BACKUP_DIR_NAMING_CONVENTION="$(date +%F_%k-%M-%S)"
  550. fi
  551. if [[ "${USE_VM_CONF}" -eq 1 ]] && [[ ! -z ${VM_ID} ]]; then
  552. reConfigureBackupParam "${VM_NAME}"
  553. dumpVMConfigurations
  554. fi
  555. VMFS_VOLUME=$(grep -E "\"${VM_NAME}\"" /tmp/vms_list | awk -F ";" '{print $3}' | sed 's/\[//;s/\]//;s/"//g')
  556. VMX_CONF=$(grep -E "\"${VM_NAME}\"" /tmp/vms_list | awk -F ";" '{print $4}' | sed 's/\[//;s/\]//;s/"//g')
  557. VMX_PATH="/vmfs/volumes/${VMFS_VOLUME}/${VMX_CONF}"
  558. VMX_DIR=$(dirname "${VMX_PATH}")
  559. #storage info
  560. if [[ ! -z ${VM_ID} ]] && [[ "${LOG_LEVEL}" != "dryrun" ]]; then
  561. storageInfo "before"
  562. fi
  563. #ignore VM as it's in the exclusion list
  564. if [ "${IGNORE_VM}" -eq 1 ]; then
  565. logger "debug" "Ignoring ${VM_NAME} for backup since its located in exclusion list\n"
  566. #checks to see if we can pull out the VM_ID
  567. elif [ -z ${VM_ID} ]; then
  568. logger "info" "ERROR: failed to locate and extract VM_ID for ${VM_NAME}!\n"
  569. VM_FAILED=1
  570. elif [ "${LOG_LEVEL}" == "dryrun" ]; then
  571. logger "dryrun" "###############################################"
  572. logger "dryrun" "Virtual Machine: $VM_NAME"
  573. logger "dryrun" "VM_ID: $VM_ID"
  574. logger "dryrun" "VMX_PATH: $VMX_PATH"
  575. logger "dryrun" "VMX_DIR: $VMX_DIR"
  576. logger "dryrun" "VMX_CONF: $VMX_CONF"
  577. logger "dryrun" "VMFS_VOLUME: $VMFS_VOLUME"
  578. logger "dryrun" "VMDK(s): "
  579. TOTAL_VM_SIZE=0
  580. getVMDKs
  581. OLD_IFS="${IFS}"
  582. IFS=":"
  583. for j in ${VMDKS};
  584. do
  585. J_VMDK=$(echo "${j}" | awk -F "###" '{print $1}')
  586. J_VMDK_SIZE=$(echo "${j}" | awk -F "###" '{print $2}')
  587. logger "dryrun" "\t${J_VMDK}\t${J_VMDK_SIZE} GB"
  588. done
  589. HAS_INDEPENDENT_DISKS=0
  590. logger "dryrun" "INDEPENDENT VMDK(s): "
  591. for k in ${INDEP_VMDKS};
  592. do
  593. HAS_INDEPENDENT_DISKS=1
  594. K_VMDK=$(echo "${k}" | awk -F "###" '{print $1}')
  595. K_VMDK_SIZE=$(echo "${k}" | awk -F "###" '{print $2}')
  596. logger "dryrun" "\t${K_VMDK}\t${K_VMDK_SIZE} GB"
  597. done
  598. IFS="${OLD_IFS}"
  599. VMDKS=""
  600. INDEP_VMDKS=""
  601. logger "dryrun" "TOTAL_VM_SIZE_TO_BACKUP: ${TOTAL_VM_SIZE} GB"
  602. if [ ${HAS_INDEPENDENT_DISKS} -eq 1 ]; then
  603. logger "dryrun" "Snapshots can not be taken for indepdenent disks!"
  604. logger "dryrun" "THIS VIRTUAL MACHINE WILL NOT HAVE ALL ITS VMDKS BACKED UP!"
  605. fi
  606. ls "${VMX_DIR}" | grep -q "\-delta\.vmdk" > /dev/null 2>&1;
  607. if [ $? -eq 0 ]; then
  608. logger "dryrun" "Snapshots found for this VM, please commit all snapshots before continuing!"
  609. logger "dryrun" "THIS VIRTUAL MACHINE WILL NOT BE BACKED UP DUE TO EXISTING SNAPSHOTS!"
  610. fi
  611. if [ ${TOTAL_VM_SIZE} -eq 0 ]; then
  612. logger "dryrun" "THIS VIRTUAL MACHINE WILL NOT BE BACKED UP DUE TO EMPTY VMDK LIST!"
  613. fi
  614. logger "dryrun" "###############################################\n"
  615. #checks to see if the VM has any snapshots to start with
  616. elif ls "${VMX_DIR}" | grep -q "\-delta\.vmdk" > /dev/null 2>&1; then
  617. logger "info" "Snapshot found for ${VM_NAME}, backup will not take place\n"
  618. VM_FAILED=1
  619. elif [[ -f "${VMX_PATH}" ]] && [[ ! -z "${VMX_PATH}" ]]; then
  620. #nfs case and backup to root path of your NFS mount
  621. if [ ${ENABLE_NON_PERSISTENT_NFS} -eq 1 ] ; then
  622. BACKUP_DIR="/vmfs/volumes/${NFS_LOCAL_NAME}/${NFS_VM_BACKUP_DIR}/${VM_NAME}"
  623. if [[ -z ${VM_NAME} ]] || [[ -z ${NFS_LOCAL_NAME} ]] || [[ -z ${NFS_VM_BACKUP_DIR} ]]; then
  624. logger "info" "ERROR: Variable BACKUP_DIR was not set properly, please ensure all required variables for non-persistent NFS backup option has been defined"
  625. exit 1
  626. fi
  627. #non-nfs (SAN,LOCAL)
  628. else
  629. BACKUP_DIR="${VM_BACKUP_VOLUME}/${VM_NAME}"
  630. if [[ -z ${VM_BACKUP_VOLUME} ]]; then
  631. logger "info" "ERROR: Variable VM_BACKUP_VOLUME was not defined"
  632. exit 1
  633. fi
  634. fi
  635. #initial root VM backup directory
  636. if [ ! -d "${BACKUP_DIR}" ]; then
  637. mkdir -p "${BACKUP_DIR}"
  638. if [ ! -d "${BACKUP_DIR}" ]; then
  639. logger "info" "Unable to create \"${BACKUP_DIR}\"! - Ensure VM_BACKUP_VOLUME was defined correctly"
  640. exit 1
  641. fi
  642. fi
  643. # directory name of the individual Virtual Machine backup followed by naming convention followed by count
  644. VM_BACKUP_DIR="${BACKUP_DIR}/${VM_NAME}-${VM_BACKUP_DIR_NAMING_CONVENTION}"
  645. # Rsync relative path variable if needed
  646. RSYNC_LINK_DIR="./${VM_NAME}-${VM_BACKUP_DIR_NAMING_CONVENTION}"
  647. mkdir -p "${VM_BACKUP_DIR}"
  648. cp "${VMX_PATH}" "${VM_BACKUP_DIR}"
  649. #new variable to keep track on whether VM has independent disks
  650. VM_HAS_INDEPENDENT_DISKS=0
  651. #extract all valid VMDK(s) from VM
  652. getVMDKs
  653. if [ ! -z ${INDEP_VMDKS} ]; then
  654. VM_HAS_INDEPENDENT_DISKS=1
  655. fi
  656. ORGINAL_VM_POWER_STATE=$(${VMWARE_CMD} vmsvc/power.getstate ${VM_ID} | tail -1)
  657. CONTINUE_TO_BACKUP=1
  658. #section that will power down a VM prior to taking a snapshot and backup and power it back on
  659. if [ ${POWER_VM_DOWN_BEFORE_BACKUP} -eq 1 ]; then
  660. START_ITERATION=0
  661. logger "info" "Powering off initiated for ${VM_NAME}, backup will not begin until VM is off..."
  662. ${VMWARE_CMD} vmsvc/power.shutdown ${VM_ID} > /dev/null 2>&1
  663. while ${VMWARE_CMD} vmsvc/power.getstate ${VM_ID} | grep -i "Powered on" > /dev/null 2>&1;
  664. do
  665. #enable hard power off code
  666. if [ ${ENABLE_HARD_POWER_OFF} -eq 1 ]; then
  667. if [ ${START_ITERATION} -ge ${ITER_TO_WAIT_SHUTDOWN} ]; then
  668. logger "info" "Hard power off occured for ${VM_NAME}, waited for $((ITER_TO_WAIT_SHUTDOWN*60)) seconds"
  669. ${VMWARE_CMD} vmsvc/power.off ${VM_ID} > /dev/null 2>&1
  670. #this is needed for ESXi, even the hard power off did not take affect right away
  671. sleep 60
  672. break
  673. fi
  674. fi
  675. logger "info" "VM is still on - Iteration: ${START_ITERATION} - sleeping for 60secs (Duration: $((START_ITERATION*60)) seconds)"
  676. sleep 60
  677. #logic to not backup this VM if unable to shutdown
  678. #after certain timeout period
  679. if [ ${START_ITERATION} -ge ${POWER_DOWN_TIMEOUT} ]; then
  680. logger "info" "Unable to power off ${VM_NAME}, waited for $((POWER_DOWN_TIMEOUT*60)) seconds! Ignoring ${VM_NAME} for backup!"
  681. VM_FAILED=1
  682. CONTINUE_TO_BACKUP=0
  683. break
  684. fi
  685. START_ITERATION=$((START_ITERATION + 1))
  686. done
  687. if [ ${CONTINUE_TO_BACKUP} -eq 1 ]; then
  688. logger "info" "VM is powerdOff"
  689. fi
  690. fi
  691. if [ ${CONTINUE_TO_BACKUP} -eq 1 ]; then
  692. logger "info" "Initiate backup for ${VM_NAME}"
  693. startTimer
  694. SNAP_SUCCESS=1
  695. VM_VMDK_FAILED=0
  696. #powered on VMs only
  697. if [[ ! ${POWER_VM_DOWN_BEFORE_BACKUP} -eq 1 ]] && [[ "${ORGINAL_VM_POWER_STATE}" != "Powered off" ]]; then
  698. SNAPSHOT_NAME="ghettoVCB-snapshot-$(date +%F)"
  699. logger "info" "Creating Snapshot \"${SNAPSHOT_NAME}\" for ${VM_NAME}"
  700. ${VMWARE_CMD} vmsvc/snapshot.create ${VM_ID} "${SNAPSHOT_NAME}" "${SNAPSHOT_NAME}" "${VM_SNAPSHOT_MEMORY}" "${VM_SNAPSHOT_QUIESCE}" > /dev/null 2>&1
  701. logger "debug" "Waiting for snapshot \"${SNAPSHOT_NAME}\" to be created"
  702. logger "debug" "Snapshot timeout set to: $((SNAPSHOT_TIMEOUT*60)) seconds"
  703. START_ITERATION=0
  704. while [ $(${VMWARE_CMD} vmsvc/snapshot.get ${VM_ID} | wc -l) -eq 1 ]
  705. do
  706. if [ ${START_ITERATION} -ge ${SNAPSHOT_TIMEOUT} ]; then
  707. logger "info" "Snapshot timed out, failed to create snapshot: \"${SNAPSHOT_NAME}\" for ${VM_NAME}"
  708. SNAP_SUCCESS=0
  709. echo "ERROR: Unable to backup ${VM_NAME} due to snapshot creation" >> ${VM_BACKUP_DIR}/STATUS.error
  710. break
  711. fi
  712. logger "debug" "Waiting for snapshot creation to be completed - Iteration: ${START_ITERATION} - sleeping for 60secs (Duration: $((START_ITERATION*30)) seconds)"
  713. sleep 60
  714. START_ITERATION=$((START_ITERATION + 1))
  715. done
  716. fi
  717. if [ ${SNAP_SUCCESS} -eq 1 ]; then
  718. OLD_IFS="${IFS}"
  719. IFS=":"
  720. for j in ${VMDKS};
  721. do
  722. VMDK=$(echo "${j}" | awk -F "###" '{print $1}')
  723. isVMDKFound=0
  724. findVMDK "${VMDK}"
  725. if [[ $isVMDKFound -eq 1 ]] || [[ "${VMDK_FILES_TO_BACKUP}" == "all" ]]; then
  726. #added this section to handle VMDK(s) stored in different datastore than the VM
  727. echo ${VMDK} | grep "^/vmfs/volumes" > /dev/null 2>&1
  728. if [ $? -eq 0 ]; then
  729. SOURCE_VMDK="${VMDK}"
  730. DS_UUID="$(echo ${VMDK#/vmfs/volumes/*})"
  731. DS_UUID="$(echo ${DS_UUID%/*/*})"
  732. VMDK_DISK="$(echo ${VMDK##/*/})"
  733. mkdir -p "${VM_BACKUP_DIR}/${DS_UUID}"
  734. DESTINATION_VMDK="${VM_BACKUP_DIR}/${DS_UUID}/${VMDK_DISK}"
  735. else
  736. SOURCE_VMDK="${VMX_DIR}/${VMDK}"
  737. DESTINATION_VMDK="${VM_BACKUP_DIR}/${VMDK}"
  738. fi
  739. #support for vRDM and deny pRDM
  740. grep "vmfsPassthroughRawDeviceMap" "${SOURCE_VMDK}" > /dev/null 2>&1
  741. if [ $? -eq 1 ]; then
  742. FORMAT_OPTION="UNKNOWN"
  743. if [ "${DISK_BACKUP_FORMAT}" == "zeroedthick" ]; then
  744. if [ "${VER}" == "4" ]; then
  745. FORMAT_OPTION="zeroedthick"
  746. else
  747. FORMAT_OPTION=""
  748. fi
  749. elif [ "${DISK_BACKUP_FORMAT}" == "2gbsparse" ]; then
  750. FORMAT_OPTION="2gbsparse"
  751. elif [ "${DISK_BACKUP_FORMAT}" == "thin" ]; then
  752. FORMAT_OPTION="thin"
  753. elif [ "${DISK_BACKUP_FORMAT}" == "eagerzeroedthick" ]; then
  754. if [ "${VER}" == "4" ]; then
  755. FORMAT_OPTION="eagerzeroedthick"
  756. else
  757. FORMAT_OPTION=""
  758. fi
  759. fi
  760. if [ "${FORMAT_OPTION}" == "UNKNOWN" ]; then
  761. logger "info" "ERROR: wrong DISK_BACKUP_FORMAT \"${DISK_BACKUP_FORMAT}\ specified for ${VM_NAME}"
  762. VM_VMDK_FAILED=1
  763. else
  764. VMDK_OUTPUT=$(mktemp /tmp/ghettovcb.XXXXXX)
  765. tail -f "${VMDK_OUTPUT}" &
  766. TAIL_PID=$!
  767. if [ -z "${FORMAT_OPTION}" ] ; then
  768. logger "debug" "${VMKFSTOOLS_CMD} -i \"${SOURCE_VMDK}\" -a \"${ADAPTER_FORMAT}\" \"${DESTINATION_VMDK}\""
  769. ${VMKFSTOOLS_CMD} -i "${SOURCE_VMDK}" -a "${ADAPTER_FORMAT}" "${DESTINATION_VMDK}" > "${VMDK_OUTPUT}" 2>&1
  770. else
  771. logger "debug" "${VMKFSTOOLS_CMD} -i \"${SOURCE_VMDK}\" -a \"${ADAPTER_FORMAT}\" -d \"${FORMAT_OPTION}\" \"${DESTINATION_VMDK}\""
  772. ${VMKFSTOOLS_CMD} -i "${SOURCE_VMDK}" -a "${ADAPTER_FORMAT}" -d "${FORMAT_OPTION}" "${DESTINATION_VMDK}" > "${VMDK_OUTPUT}" 2>&1
  773. fi
  774. VMDK_EXIT_CODE=$?
  775. kill "${TAIL_PID}"
  776. cat "${VMDK_OUTPUT}" >> "${REDIRECT}"
  777. echo >> "${REDIRECT}"
  778. echo
  779. rm "${VMDK_OUTPUT}"
  780. if [ "${VMDK_EXIT_CODE}" != 0 ] ; then
  781. logger "info" "ERROR: error in backing up of \"${SOURCE_VMDK}\" for ${VM_NAME}"
  782. VM_VMDK_FAILED=1
  783. fi
  784. fi
  785. else
  786. logger "info" "WARNING: A physical RDM \"${SOURCE_VMDK}\" was found for ${VM_NAME}, which will not be backed up"
  787. VM_VMDK_FAILED=1
  788. fi
  789. fi
  790. done
  791. IFS="${OLD_IFS}"
  792. fi
  793. #powered on VMs only w/snapshots
  794. if [[ ${SNAP_SUCCESS} -eq 1 ]] && [[ ! ${POWER_VM_DOWN_BEFORE_BACKUP} -eq 1 ]] && [[ "${ORGINAL_VM_POWER_STATE}" == "Powered on" ]] || [[ "${ORGINAL_VM_POWER_STATE}" == "Suspended" ]]; then
  795. ${VMWARE_CMD} vmsvc/snapshot.remove ${VM_ID} > /dev/null 2>&1
  796. #do not continue until all snapshots have been committed
  797. logger "info" "Removing snapshot from ${VM_NAME} ..."
  798. while ls "${VMX_DIR}" | grep -q "\-delta\.vmdk";
  799. do
  800. sleep 5
  801. done
  802. fi
  803. if [[ ${POWER_VM_DOWN_BEFORE_BACKUP} -eq 1 ]] && [[ "${ORGINAL_VM_POWER_STATE}" == "Powered on" ]]; then
  804. #power on vm that was powered off prior to backup
  805. logger "info" "Powering back on ${VM_NAME}"
  806. ${VMWARE_CMD} vmsvc/power.on ${VM_ID} > /dev/null 2>&1
  807. fi
  808. TMP_IFS=${IFS}
  809. IFS=${ORIG_IFS}
  810. if [ ${ENABLE_COMPRESSION} -eq 1 ]; then
  811. COMPRESSED_ARCHIVE_FILE="${BACKUP_DIR}/${VM_NAME}-${VM_BACKUP_DIR_NAMING_CONVENTION}.gz"
  812. logger "info" "Compressing VM backup \"${COMPRESSED_ARCHIVE_FILE}\"..."
  813. if [ ${IS_4I} -eq 1 ]; then
  814. busybox tar -cz -C "${BACKUP_DIR}" "${VM_NAME}-${VM_BACKUP_DIR_NAMING_CONVENTION}" -f "${COMPRESSED_ARCHIVE_FILE}"
  815. else
  816. tar -cz -C "${BACKUP_DIR}" "${VM_NAME}-${VM_BACKUP_DIR_NAMING_CONVENTION}" -f "${COMPRESSED_ARCHIVE_FILE}"
  817. fi
  818. # verify compression
  819. if [[ $? -eq 0 ]] && [[ -f "${COMPRESSED_ARCHIVE_FILE}" ]]; then
  820. logger "info" "Successfully compressed backup for ${VM_NAME}!\n"
  821. COMPRESSED_OK=1
  822. else
  823. logger "info" "Error in compressing ${VM_NAME}!\n"
  824. COMPRESSED_OK=0
  825. fi
  826. rm -rf "${VM_BACKUP_DIR}"
  827. checkVMBackupRotation "${BACKUP_DIR}" "${VM_NAME}"
  828. else
  829. checkVMBackupRotation "${BACKUP_DIR}" "${VM_NAME}"
  830. fi
  831. IFS=${TMP_IFS}
  832. VMDKS=""
  833. INDEP_VMDKS=""
  834. endTimer
  835. if [ ${SNAP_SUCCESS} -ne 1 ]; then
  836. logger "info" "ERROR: Unable to backup ${VM_NAME} due to snapshot creation!\n"
  837. [[ ${ENABLE_COMPRESSION} -eq 1 ]] && [[ $COMPRESSED_OK -eq 1 ]] || echo "ERROR: Unable to backup ${VM_NAME} due to snapshot creation" >> ${VM_BACKUP_DIR}/STATUS.error
  838. VM_FAILED=1
  839. elif [ ${VM_VMDK_FAILED} -ne 0 ]; then
  840. logger "info" "ERROR: Unable to backup ${VM_NAME} due to error in VMDK backup!\n"
  841. [[ ${ENABLE_COMPRESSION} -eq 1 ]] && [[ $COMPRESSED_OK -eq 1 ]] || echo "ERROR: Unable to backup ${VM_NAME} due to error in VMDK backup" >> ${VM_BACKUP_DIR}/STATUS.error
  842. VMDK_FAILED=1
  843. elif [ ${VM_HAS_INDEPENDENT_DISKS} -eq 1 ]; then
  844. logger "info" "WARN: ${VM_NAME} has some Independent VMDKs that can not be backed up!\n";
  845. [[ ${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
  846. VMDK_FAILED=1
  847. else
  848. logger "info" "Successfully completed backup for ${VM_NAME}!\n"
  849. [[ ${ENABLE_COMPRESSION} -eq 1 ]] && [[ $COMPRESSED_OK -eq 1 ]] || echo "Successfully completed backup" > ${VM_BACKUP_DIR}/STATUS.ok
  850. VM_OK=1
  851. #experimental
  852. #create symlink for the very last backup to support rsync functionality for additinal replication
  853. if [ "${RSYNC_LINK}" -eq 1 ]; then
  854. SYMLINK_DST=${VM_BACKUP_DIR}
  855. if [ ${ENABLE_COMPRESSION} -eq 1 ]; then
  856. SYMLINK_DST1="${RSYNC_LINK_DIR}.gz"
  857. else
  858. SYMLINK_DST1=${RSYNC_LINK_DIR}
  859. fi
  860. SYMLINK_SRC="$(echo "${SYMLINK_DST%*-*-*-*_*-*-*}")-symlink"
  861. logger "info" "Creating symlink \"${SYMLINK_SRC}\" to \"${SYMLINK_DST1}\""
  862. ln -s "${SYMLINK_DST1}" "${SYMLINK_SRC}"
  863. fi
  864. #storage info after backup
  865. storageInfo "after"
  866. fi
  867. else
  868. if [ ${CONTINUE_TO_BACKUP} -eq 0 ]; then
  869. logger "info" "ERROR: Failed to backup ${VM_NAME}!\n"
  870. VM_FAILED=1
  871. else
  872. logger "info" "ERROR: Failed to lookup ${VM_NAME}!\n"
  873. VM_FAILED=1
  874. fi
  875. fi
  876. fi
  877. done
  878. unset IFS
  879. if [[ ${ENABLE_NON_PERSISTENT_NFS} -eq 1 ]] && [[ ${UNMOUNT_NFS} -eq 1 ]] && [[ "${LOG_LEVEL}" != "dryrun" ]]; then
  880. logger "debug" "Sleeping for 30seconds before unmounting NFS volume"
  881. sleep 30
  882. ${VMWARE_CMD} hostsvc/datastore/destroy ${NFS_LOCAL_NAME}
  883. fi
  884. }
  885. getFinalStatus() {
  886. if [[ "${LOG_TYPE}" == "dryrun" ]]; then
  887. FINAL_STATUS="###### Final status: OK, only a dryrun. ######"
  888. LOG_STATUS="OK"
  889. EXIT=0
  890. elif [[ $VM_OK == 1 ]] && [[ $VM_FAILED == 0 ]] && [[ $VMDK_FAILED == 0 ]]; then
  891. FINAL_STATUS="###### Final status: All VMs backed up OK! ######"
  892. LOG_STATUS="OK"
  893. EXIT=0
  894. elif [[ $VM_OK == 1 ]] && [[ $VM_FAILED == 0 ]] && [[ $VMDK_FAILED == 1 ]]; then
  895. FINAL_STATUS="###### Final status: WARNING: All VMs backed up, but some disk(s) failed! ######"
  896. LOG_STATUS="WARNING"
  897. EXIT=3
  898. elif [[ $VM_OK == 1 ]] && [[ $VM_FAILED == 1 ]] && [[ $VMDK_FAILED == 0 ]]; then
  899. FINAL_STATUS="###### Final status: ERROR: Only some of the VMs backed up! ######"
  900. LOG_STATUS="ERROR"
  901. EXIT=4
  902. elif [[ $VM_OK == 1 ]] && [[ $VM_FAILED == 1 ]] && [[ $VMDK_FAILED == 1 ]]; then
  903. FINAL_STATUS="###### Final status: ERROR: Only some of the VMs backed up, and some disk(s) failed! ######"
  904. LOG_STATUS="ERROR"
  905. EXIT=5
  906. elif [[ $VM_OK == 0 ]] && [[ $VM_FAILED == 1 ]]; then
  907. FINAL_STATUS="###### Final status: ERROR: All VMs failed! ######"
  908. LOG_STATUS="ERROR"
  909. EXIT=6
  910. elif [[ $VM_OK == 0 ]]; then
  911. FINAL_STATUS="###### Final status: ERROR: No VMs backed up! ######"
  912. LOG_STATUS="ERROR"
  913. EXIT=7
  914. fi
  915. logger "info" "$FINAL_STATUS\n"
  916. }
  917. buildHeaders() {
  918. EMAIL_ADDRESS=$1
  919. echo -ne "HELO $(hostname -s)\r\n" > "${EMAIL_LOG_HEADER}"
  920. echo -ne "MAIL FROM: <${EMAIL_FROM}>\r\n" >> "${EMAIL_LOG_HEADER}"
  921. echo -ne "RCPT TO: <${EMAIL_ADDRESS}>\r\n" >> "${EMAIL_LOG_HEADER}"
  922. echo -ne "DATA\r\n" >> "${EMAIL_LOG_HEADER}"
  923. echo -ne "From: ${EMAIL_FROM}\r\n" >> "${EMAIL_LOG_HEADER}"
  924. echo -ne "To: ${EMAIL_ADDRESS}\r\n" >> "${EMAIL_LOG_HEADER}"
  925. echo -ne "Subject: ghettoVCB - ${FINAL_STATUS}\r\n" >> "${EMAIL_LOG_HEADER}"
  926. echo -en ".\r\n" >> "${EMAIL_LOG_OUTPUT}"
  927. echo -en "QUIT\r\n" >> "${EMAIL_LOG_OUTPUT}"
  928. cat "${EMAIL_LOG_HEADER}" > "${EMAIL_LOG_CONTENT}"
  929. cat "${EMAIL_LOG_OUTPUT}" >> "${EMAIL_LOG_CONTENT}"
  930. }
  931. sendMail() {
  932. #close email message
  933. if [ "${EMAIL_LOG}" -eq 1 ]; then
  934. echo "${EMAIL_TO}" | grep "," > /dev/null 2>&1
  935. if [ $? -eq 0 ]; then
  936. ORIG_IFS=${IFS}
  937. IFS=','
  938. for i in ${EMAIL_TO};
  939. do
  940. buildHeaders ${i}
  941. "${NC_BIN}" "${EMAIL_SERVER}" "${EMAIL_SERVER_PORT}" < "${EMAIL_LOG_CONTENT}" > /dev/null 2>&1
  942. if [ $? -eq 1 ]; then
  943. logger "info" "ERROR: Failed to email log output to ${EMAIL_SERVER}:${EMAIL_SERVER_PORT} to ${EMAIL_TO}\n"
  944. fi
  945. done
  946. unset IFS
  947. else
  948. buildHeaders ${EMAIL_TO}
  949. "${NC_BIN}" "${EMAIL_SERVER}" "${EMAIL_SERVER_PORT}" < "${EMAIL_LOG_CONTENT}" > /dev/null 2>&1
  950. if [ $? -eq 1 ]; then
  951. logger "info" "ERROR: Failed to email log output to ${EMAIL_SERVER}:${EMAIL_SERVER_PORT} to ${EMAIL_TO}\n"
  952. fi
  953. fi
  954. if [ "${EMAIL_DEBUG}" -eq 1 ]; then
  955. logger "info" "Email log output will not be deleted and is stored in ${EMAIL_LOG_CONTENT}"
  956. else
  957. logger "info" "Removing ${EMAIL_LOG_OUTPUT} and ${EMAIL_LOG_HEADER} and ${EMAIL_LOG_CONTENT}"
  958. rm -f "${EMAIL_LOG_OUTPUT}"
  959. rm -f "${EMAIL_LOG_HEADER}"
  960. rm -f "${EMAIL_LOG_CONTENT}"
  961. fi
  962. fi
  963. }
  964. ####################
  965. # #
  966. # Start of Script #
  967. # #
  968. ####################
  969. IS_4I=0
  970. if [ ! -f /bin/bash ]; then
  971. IS_4I=1
  972. fi
  973. USE_VM_CONF=0
  974. USE_GLOBAL_CONF=0
  975. BACKUP_ALL_VMS=0
  976. EXCLUDE_SOME_VMS=0
  977. EMAIL_LOG_HEADER=/tmp/ghettoVCB-email-$$.header
  978. EMAIL_LOG_OUTPUT=/tmp/ghettoVCB-email-$$.log
  979. EMAIL_LOG_CONTENT=/tmp/ghettoVCB-email-$$.content
  980. #read user input
  981. while getopts ":af:c:g:l:d:e:" ARGS; do
  982. case $ARGS in
  983. a)
  984. BACKUP_ALL_VMS=1
  985. VM_FILE="/tmp/backup_all_vms_on-$(hostname)"
  986. touch "${VM_FILE}"
  987. ;;
  988. f) VM_FILE="${OPTARG}"
  989. ;;
  990. e)
  991. VM_EXCLUSION_FILE="${OPTARG}"
  992. EXCLUDE_SOME_VMS=1
  993. ;;
  994. c)
  995. CONFIG_DIR="${OPTARG}"
  996. USE_VM_CONF=1
  997. ;;
  998. g)
  999. GLOBAL_CONF="${OPTARG}"
  1000. USE_GLOBAL_CONF=1
  1001. ;;
  1002. l)
  1003. LOG_OUTPUT="${OPTARG}"
  1004. ;;
  1005. d)
  1006. LOG_LEVEL="${OPTARG}"
  1007. ;;
  1008. :)
  1009. echo "Option -${OPTARG} requires an argument."
  1010. exit 1
  1011. ;;
  1012. *)
  1013. printUsage
  1014. exit 1
  1015. ;;
  1016. esac
  1017. done
  1018. #performs a check on the number of commandline arguments + verifies $2 is a valid file
  1019. sanityCheck $#
  1020. LOCKDIR=/tmp/ghettoVCB.lock
  1021. if mkdir "${LOCKDIR}"
  1022. then
  1023. GHETTOVCB_PID=$$
  1024. logger "info" "============================== ghettoVCB LOG START ==============================\n"
  1025. logger "debug" "Succesfully acquired lock directory - ${LOCKDIR}\n"
  1026. # Remove lockdir when the script finishes, or when it receives a signal
  1027. trap 'rm -rf "${LOCKDIR}"' 0 # remove directory when script finishes
  1028. trap "exit 2" 1 2 3 13 15 # terminate script when receiving signal
  1029. ghettoVCB ${VM_FILE}
  1030. getFinalStatus
  1031. logger "debug" "Succesfully removed lock directory - ${LOCKDIR}\n"
  1032. logger "info" "============================== ghettoVCB LOG END ================================\n"
  1033. sendMail
  1034. rm -rf "${LOCKDIR}"
  1035. exit $EXIT
  1036. else
  1037. logger "info" "Failed to acquire lock, another instance of script may be running, giving up on ${LOCKDIR}\n"
  1038. exit 1
  1039. fi