#!/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 XAPI 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=12
metadata_version=1
debug=/bin/true

function usage {
    echo "Usage: $0 [-h] [-i] [-u <SR UUID>]"
    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 " -f  Force backup even when less than 10% free capacity is left on the backup VDI"
    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 uuid5 {
  # could use a modern uuidgen but it's not on XS 8
  # should work with Python 2 and 3
  python3 -c "import uuid; print (uuid.uuid5(uuid.UUID('$1'), '$2'))"
}

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
}

leave_mounted=0
init_fs=0
create_vdi=0 
just_find_vdi=0
fs_uninitialised=0
usage_alert=90
force_backup=0
while getopts "yhvink:u:dcf" 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="" ;;
    f) force_backup=1 ;;
    *) 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 vaid
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
test_sr "${sr_uuid}"

sr_name=$(${XE} sr-param-get uuid="${sr_uuid}" param-name=name-label)
# assume use of the new format predictable UUID
vdi_uuid=$(${XE} vdi-list uuid="$(uuid5 "e93e0639-2bdb-4a59-8b46-352b3f408c19" "$sr_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
      ${debug} echo -n "Destroying VBD: "
      ${XE} vbd-destroy uuid="${vbd_uuid}"
   fi
   if [ ${fs_uninitialised} -eq 1 -a -n "${vdi_uuid}" ] ; then
      ${XE} vdi-destroy uuid="${vdi_uuid}"
   fi
}

echo "Using SR: ${sr_name}"
if [ -z "${vdi_uuid}" ]; then
  if [ "${create_vdi}" -gt 0 ]; then
    echo -n "Creating new backup VDI: "
    label="Pool Metadata Backup"
    # the label must match what xapi_vdi.ml is using for backup VDIs
    vdi_uuid=$(${XE} vdi-create virtual-size=1GiB sr-uuid="${sr_uuid}" type=user name-label="${label}")
    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
else
  ${debug} echo "Using existing backup VDI: ${vdi_uuid}"
  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

  # check the usage of the backup VDI
  usage=`cd "${mnt}" && df . | sed -n "2p" | awk '{ print $5 }' | tr -d '%'`
  echo "Checking backup VDI space usage: $usage%"
  if [ "$usage" -gt "$usage_alert" ] && [ "${force_backup}" -eq 0 ]; then
    echo "Running out of space, you can use '-d' option to attach VDI and free more space, exit now."
    cleanup
    exit 1
  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
