#!/bin/bash # Script which backups metadata into a VDI # Citrix Systems Inc, 2008 trap "cleanup" TERM INT if [ ! -e /etc/xensource-inventory ]; then echo Must run on a XenServer host. exit 1 fi . /etc/xensource-inventory XE="/opt/xensource/bin/xe" master_uuid=$(${XE} pool-list params=master --minimal) if [ $? -gt 0 ]; then echo Error: unable to determine master host uuid exit 1 fi if [ "${master_uuid}" != "${INSTALLATION_UUID}" ]; then echo Error: must run this script on the master host in the resource pool. exit 1 fi history_kept=25 metadata_version=1 debug=/bin/true function usage { echo "Usage: $0 [-h] [-i] [-u ]" echo echo " -h: Display this help message" echo " -c: Create a backup VDI if one cannot be found (default: false)" echo " -i: Initialize the backup VDI before performing the backup" echo " -u: UUID of the SR you wish to backup to" echo " -d: Dont perform a backup, but leave the disk mounted in a shell" echo " -k: Number of older backups to preserve (default: ${history_kept})" echo " -n: Just try to find a backup VDI and stop the script after that" echo " -v: Verbose output" echo echo echo "Use the -d option to examine the backup VDI, and restore it if necessary." echo "It is a good idea to copy the backup onto the control domain before" echo "restoring it via the xe pool-database-restore CLI command." exit 1 } function test_sr { sr_uuid_found=$(${XE} sr-list uuid="$1" --minimal) if [ "${sr_uuid_found}" != "$1" ]; then echo Invalid SR UUID specified: $1 usage fi # JKB: Determine SR free space sr_capacity=$(${XE} sr-list params=physical-size uuid="$1" --minimal) sr_utilization=$(${XE} sr-list params=physical-utilisation uuid="$1" --minimal) sr_space=$((sr_capacity-sr_utilization)) if [ "$sr_space" -lt "262144000" ]; then echo SR \"$2\" does not have enough free space available exit 1 fi } leave_mounted=0 init_fs=0 create_vdi=0 just_find_vdi=0 fs_uninitialised=0 while getopts "hvink:u:dc" opt ; do case $opt in h) usage ;; c) create_vdi=1 ; fs_uninitialised=1 ;; i) init_fs=1 ; fs_uninitialised=1 ;; k) history_kept=${OPTARG} ;; u) sr_uuid=${OPTARG} ;; d) leave_mounted=1 ;; n) just_find_vdi=1 ;; v) debug="" ;; *) echo "Invalid option"; usage ;; esac done # get pool uuid IFS=, pool_uuid=$(${XE} pool-list params=uuid --minimal) if [ -z "${pool_uuid}" ]; then echo Unable to determine pool UUID. exit 1 fi # Determine if the SR UUID is valid if [ -z "${sr_uuid}" ]; then # use the default-SR from the pool sr_uuid=$(${XE} pool-param-get uuid=${pool_uuid} param-name=default-SR) fi # Determine SR NAME sr_name=$(${XE} sr-param-get uuid=${sr_uuid} param-name=name-label) # Test the SR for free space, read/write capability, etc test_sr "${sr_uuid}" "${sr_name}" # see if a backup VDI already exists on the selected SR vdi_uuid=$(${XE} vdi-list other-config:ctxs-pool-backup=true sr-uuid=${sr_uuid} params=uuid --minimal) mnt= function cleanup { trap "" TERM INT cd / if [ ! -z "${mnt}" ]; then umount ${mnt} >/dev/null 2>&1 rmdir ${mnt} fi if [ ! -z "${vbd_uuid}" ]; then ${debug} echo -n "Unplugging VBD: " ${XE} vbd-unplug uuid=${vbd_uuid} timeout=20 # poll for the device to go away if we know its name if [ "${device}" != "" ]; then device_gone=0 for ((i=0; i<10; i++)); do ${debug} echo -n "." if [ ! -b ${device} ]; then ${debug} echo " done" device_gone=1 break fi sleep 1 done if [ ${device_gone} -eq 0 ]; then ${debug} echo " failed" echo Please destroy VBD ${vbd_uuid} manually. else ${XE} vbd-destroy uuid=${vbd_uuid} fi fi fi if [ ${fs_uninitialised} -eq 1 -a -n "${vdi_uuid}" ] ; then ${XE} vdi-destroy uuid=${vdi_uuid} fi } echo Using SR: ${sr_name} backupdate=$(date +%F-%H-%M-%S) if [ -z "${vdi_uuid}" ]; then if [ "${create_vdi}" -gt 0 ]; then echo -n "Creating new backup VDI: " vdi_uuid=$(${XE} vdi-create virtual-size=250MiB sr-uuid=${sr_uuid} type=user name-label="Pool Metadata Backup" ) init_fs=1 if [ $? -ne 0 ]; then echo failed exit 1 fi else echo "Backup VDI not found, aborting. You can initialise one using the '-c' flag." exit 3 fi echo ${vdi_uuid} ${XE} vdi-param-set uuid=${vdi_uuid} other-config:ctxs-pool-backup=true name-description="Created on ${backupdate}" else ${debug} echo "Using existing backup VDI: ${vdi_uuid}" ${XE} vdi-param-set uuid=${vdi_uuid} other-config:ctxs-pool-backup=true name-description="Updated on ${backupdate}" fs_uninitialised=0 fi if [ ${just_find_vdi} -gt 0 ]; then exit 0 fi ${debug} echo -n "Creating VBD: " vbd_uuid=$(${XE} vbd-create vm-uuid=${CONTROL_DOMAIN_UUID} vdi-uuid=${vdi_uuid} device=autodetect) ${debug} echo ${vbd_uuid} if [ $? -ne 0 -o -z "${vbd_uuid}" ]; then echo error creating VBD cleanup exit 1 fi ${debug} echo -n "Plugging VBD: " ${XE} vbd-plug uuid=${vbd_uuid} device=/dev/$(${XE} vbd-param-get uuid=${vbd_uuid} param-name=device) if [ ! -b ${device} ]; then ${debug} echo ${device}: not a block special cleanup exit 1 fi ${debug} echo ${device} if [ $init_fs -eq 1 ]; then ${debug} echo -n "Creating filesystem: " mkfs.ext3 -j -F ${device} > /dev/null 2>&1 ${debug} echo "done" fs_uninitialised=0 fi ${debug} echo -n "Mounting filesystem: " mnt=/var/run/pool-backup-${vdi_uuid} mkdir -p ${mnt} /sbin/fsck -a ${device} >/dev/null 2>&1 if [ $? -ne 0 ]; then ${debug} fsck failed. Please correct manually cleanup exit 1 fi mount ${device} ${mnt} > /dev/null 2>&1 if [ $? -ne 0 ]; then ${debug} echo failed cleanup exit 1 fi ${debug} echo ${mnt} if [ ${leave_mounted} -eq 0 ]; then lrconf=${mnt}/conf/${vdi_uuid} if [ ! -f ${lrconf} ]; then ${debug} echo -n "Initialising rotation: " mkdir -p ${mnt}/conf/ echo "${mnt}/${pool_uuid}.db {" >> ${lrconf} echo " rotate ${history_kept}" >> ${lrconf} echo " missingok" >> ${lrconf} echo "}" >> ${lrconf} echo done echo ${metadata_version} >> ${mnt}/.ctxs-metadata-backup fi # invoke logrotate to rotate over old pool db backups echo -n "Rotating old backups: " logrotate -f ${lrconf} num_found=$(find ${mnt} -name \*.db\.* | wc -l) echo found ${num_found} # perform the pool database dump echo -n "Backing up pool database: " ${XE} pool-dump-database file-name=${mnt}/${pool_uuid}.db echo done # backup the VM metadata for each VM in the pool into a dated directory datetime=$(date +%F-%H-%M-%S) metadir=${mnt}/metadata/${datetime} mkdir -p ${metadir} echo -n "Cleaning old VM metadata: " IFS=" " todelete=$(cd ${mnt}/metadata && ls -1 |sort -n | head -n -${history_kept} | xargs echo) for dir in ${todelete}; do rm -rf ${mnt}/metadata/${dir} done echo done IFS="," echo -n "Backing up SR metadata: " mkdir -p ${metadir} "/opt/xensource/libexec/backup-sr-metadata.py" -f ${metadir}/SRMETA.xml echo "done" echo -n "Backing up VM metadata: " ${debug} echo "" mkdir -p ${metadir}/all for vmuuid in $(${XE} vm-list params=uuid is-control-domain=false --minimal); do ${debug} echo -n . ${XE} vm-export --metadata uuid=${vmuuid} filename=${metadir}/all/${vmuuid}.vmmeta >/dev/null 2>&1 done echo "done" echo -n "Backing up Template metadata: " ${debug} echo "" template_uuids=$("/opt/xensource/libexec/print-custom-templates") if [ $? -eq 0 ]; then for tmpl_uuid in ${template_uuids}; do ${XE} template-export --metadata template-uuid=${tmpl_uuid} filename=${metadir}/all/${tmpl_uuid}.vmmeta >/dev/null 2>&1 done fi echo "done" "/opt/xensource/libexec/link-vms-by-sr.py" -d ${metadir} else cd ${mnt} env PS1="Mounted backup VDI on: ${mnt}\nPress ^D to exit shell and safely detach it.\n\n[\u@\h \W]\$ " bash fi cleanup