RunBefore and RunAfter using Azure Application Consistent Snapshot tool

This article provides a guide for using the --runbefore and --runafter capability of the Azure Application Consistent Snapshot tool that you can use with Azure NetApp Files.

Introduction

AzAcSnap can execute external commands before or after its main execution using the options --runbefore or --runafter respectively.

--runbefore will run a shell command before the main execution of azacsnap and provides some of the azacsnap command-line parameters to the shell environment. By default, azacsnap will wait up to 30 seconds for the external shell command to complete before killing the process and returning to azacsnap normal execution. This delay can be overridden by adding a number to wait in seconds after a % character (for example, --runbefore "mycommand.sh%60" will wait up to 60 seconds for mycommand.sh to complete).

--runafter will run a shell command after the main execution of azacsnap and provides some of the azacsnap command-line parameters to the shell environment. By default, azacsnap will wait up to 30 seconds for the external shell command to complete before killing the process and returning to azacsnap normal execution. This can be overridden by adding a number to wait in seconds after a % character (for example, --runafter "mycommand.sh%60" will wait for up to 60 seconds for mycommand.sh to complete).

The following list of environment variables is generated by azacsnap and passed to the shell forked to run the commands provided as parameters to --runbefore and --runafter:

  • $azCommand = the command option passed to -c (for example, backup, test, etc.).
  • $azConfigFileName = the configuration filename.
  • $azPrefix = the --prefix value.
  • $azRetention = the --retention value.
  • $azSid = the --dbsid value.
  • $azSnapshotName = the snapshot name generated by azacsnap.

Note

There's only a value for $azSnapshotName in the --runafter option.

Example usage

An example usage for this new feature is to upload a snapshot to Azure Blob for archival purposes using the azcopy tool (Copy or move data to Azure Storage by using AzCopy).

The following crontab entry is a single line and runs azacsnap at five past midnight. Note the call to snapshot-to-blob.sh passing the snapshot name and snapshot prefix:

5 0 * * *         ( . ~/.bash_profile ; cd /home/azacsnap/bin ; ./azacsnap -c backup --volume data --prefix daily --retention 1 --configfile HANA.json --trim --ssl openssl --runafter 'env ; ./snapshot-to-blob.sh $azSnapshotName $azPrefix')

This example shell script has a special stanza at the end to prevent AzAcSnap from killing the external command due to the timeout described earlier. This allows for a long running command, such as uploading large files with azcopy, to be run without being prematurely stopped.

The snapshots need to be mounted on the system doing the copy, with at a minimum read-only privilege. The base location of the mount point for the snapshots should be provided to the sourceDir variable in the script.

cat snapshot-to-blob.sh
#!/bin/bash
# Utility to upload-to/list Azure Blob store.
#   If run as snapshot-to-blob.sh will upload a gzipped tarball of the snapshot.
#   If run as list-blobs.sh will list uploaded blobs.
#     e.g. `ln -s snapshot-to-blob.sh list-blobs.sh`


# _START_ Change these
SAS_KEY_FILE="${HOME}/bin/blob-credentials.saskey"
# the snapshots need to be mounted locally for copying, put source directory here
SOURCE_DIR="/mnt/saphana1/hana_data_PR1/.snapshot"
# _END_ Change these


# _START_ AzCopy Settings
#Overrides where the job plan files (used for progress tracking and resuming) are stored, to avoid filling up a disk.
export AZCOPY_JOB_PLAN_LOCATION="${HOME}/.azcopy/plans/"
#Overrides where the log files are stored, to avoid filling up a disk.
export AZCOPY_LOG_LOCATION="${HOME}/.azcopy/logs/"
#If set, to anything, on-screen output will include counts of chunks by state
export AZCOPY_SHOW_PERF_STATES=true
# _END_ AzCopy Settings


# do not change any of the following


# Make sure we got some command line args
if [ "$(basename "$0")" = "snapshot-to-blob.sh" ] && ([ "$1" = "" ] || [ "$2" = "" ]); then
  echo "Usage: $0 <SNAPSHOT_NAME> <PREFIX>"
  exit 1
fi

# Make sure we can read the SAS key credential file.
if [ -r "${SAS_KEY_FILE}" ]; then
  source "${SAS_KEY_FILE}"
else
  echo "Credential file '${SAS_KEY_FILE}' not found, exiting!"
fi


# Assign the rest of the Global variables.
SNAPSHOT_NAME=$1
PREFIX=$2
BLOB_STORE="$(echo "${PORTAL_GENERATED_SAS}" | cut -f1 -d'?')"
BLOB_SAS_KEY="$(echo "${PORTAL_GENERATED_SAS}" | cut -f2 -d'?')"
ARCHIVE_LOG="logs/$(basename "$0").log"

# Archive naming (daily.1, daily.2, etc...)
DAY_OF_WEEK=$(date "+%u")
MONTH_OF_YEAR=$(date "+%m")
ARCHIVE_BLOB_TGZ="${PREFIX}.${DAY_OF_WEEK}.tgz"

#######################################
# Write to the log.
# Globals:
#   None
# Arguments:
#   LOG_MSG
#######################################
write_log(){
  LOG_MSG=$1
  date=$(date "+[%d/%h/%Y:%H:%M:%S %z]")
  echo "$date ${LOG_MSG}" >> "${ARCHIVE_LOG}"
}


#######################################
# Run and Log the command.
# Globals:
#   None
# Arguments:
#   CMD_TO_RUN
#######################################
run_cmd(){
  CMD_TO_RUN="${1}"
  write_log "[RUNCMD] ${CMD_TO_RUN}"
  bash -c "${CMD_TO_RUN}"
}


#######################################
# Check snapshot exists and then background the upload to Blob store.
# Globals:
#   SOURCE_DIR
#   SNAPSHOT_NAME
#   ARCHIVE_LOG
# Arguments:
#   None
#######################################
snapshot_to_blob(){
  # Check SOURCE_DIR and SNAPSHOT_NAME exist
  if [ ! -d "${SOURCE_DIR}/${SNAPSHOT_NAME}" ]; then
    echo "${SOURCE_DIR}/${SNAPSHOT_NAME} not found, exiting!" | tee -a "${ARCHIVE_LOG}"
    exit 1
  fi
  # background ourselves so AzAcSnap exits cleanly
  echo "Backgrounding '$0 $@' to prevent blocking azacsnap"
  echo "write_logging to ${ARCHIVE_LOG}"
  {
    trap '' HUP
    # the script
    upload_to_blob
    list_blob >> "${ARCHIVE_LOG}"
  } < /dev/null > /dev/null 2>&1 &
}


#######################################
# Upload to Blob store.
# Globals:
#   SOURCE_DIR
#   SNAPSHOT_NAME
#   ARCHIVE_BLOB_TGZ
#   BLOB_STORE
#   BLOB_SAS_KEY
#   ARCHIVE_LOG
# Arguments:
#   None
#######################################
upload_to_blob(){
  # Copy snapshot to blob store
  echo "Starting upload of ${SNAPSHOT_NAME} to ${BLOB_STORE}/${ARCHIVE_BLOB_TGZ}" >> "${ARCHIVE_LOG}"
  run_cmd "azcopy env ; cd ${SOURCE_DIR}/${SNAPSHOT_NAME} && tar zcvf - * | azcopy cp \"${BLOB_STORE}/${ARCHIVE_BLOB_TGZ}?${BLOB_SAS_KEY}\" --from-to PipeBlob && cd -"
  echo "Completed upload of ${SNAPSHOT_NAME} ${BLOB_STORE}/${ARCHIVE_BLOB_TGZ}" >> "${ARCHIVE_LOG}"

  # Complete
  echo "Finished ($0 ${SNAPSHOT_NAME} ${PREFIX}) @ $(date "+%d-%h-%Y %H:%M")" >> "${ARCHIVE_LOG}"
  echo "--------------------------------------------------------------------------------" >> "${ARCHIVE_LOG}"
  # col 12345678901234567890123456789012345678901234567890123456789012345678901234567890
}


#######################################
# List contents of Blob store.
# Globals:
#   BLOB_STORE
#   BLOB_SAS_KEY
# Arguments:
#   None
#######################################
list_blob(){
  LOG_MSG="Current list of files stored in ${BLOB_STORE}"
  write_log "${LOG_MSG}"
  echo "${LOG_MSG}"
  run_cmd "azcopy list \"${BLOB_STORE}?${BLOB_SAS_KEY}\"  --properties LastModifiedTime "
}


# Log when script started.
write_log "Started ($0 ${SNAPSHOT_NAME} ${PREFIX}) @ $(date "+%d-%h-%Y %H:%M")"


# Check what this was called as ($0) and run accordingly.
case "$(basename "$0")" in
  "snapshot-to-blob.sh" )
    snapshot_to_blob
    ;;
  "list-blobs.sh" )
    list_blob
    ;;
  *)
    echo "Command '$0' not recognised!"
    ;;
esac

The saskeyFile contains the following example SAS Key (content changed for security):

cat blob-credentials.saskey
# we need a generated SAS key, get this from the portal with read,add,create,write,list permissions
PORTAL_GENERATED_SAS="https://<targetstorageaccount>.blob.core.windows.net/<blob-store>?sp=racwl&st=2021-06-10T21:10:38Z&se=2021-06-11T05:10:38Z&spr=https&sv=2020-02-10&sr=c&sig=<key-material>"

Next steps