From 8d48656d2feffc292d64efc4054e374a5ca3ba47 Mon Sep 17 00:00:00 2001 From: Gardouille Date: Tue, 18 Nov 2014 16:02:20 +0100 Subject: [PATCH] Small script to take ZFS snapshot periodically and send it to a remote host. Will be replace by zfSnap soon (many pull request to add a send option), maybe in v2.1. --- snapsend.sh | 172 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 172 insertions(+) create mode 100755 snapsend.sh diff --git a/snapsend.sh b/snapsend.sh new file mode 100755 index 0000000..f3f7418 --- /dev/null +++ b/snapsend.sh @@ -0,0 +1,172 @@ +#!/bin/sh +# +# 2014/11/14 - Initial script creation +# +# Based on snapxfer script by pds from zpool.org +# https://zpool.org/zfs-snapshots-and-remote-replication/ +# +# Also main use zfSnap (https://github.com/zfsnap/zfsnap/wiki/zfSnap) +# Must take the 1.x version (tree legacy). Too many changes in the 2.0.0 beta. + +# Local ZFS filesystems that will have a snapshot +DATASETS="datastore/vm" + +# Destination host for snapshot replication +# The user must have enought rights to use `zfs command` +DHOST="root@192.168.42.52" + +# Output logfile +LOGFILE="/var/log/snapsend/snapsend.log" + +# Tools that help implement snapshot logic (and transfer soon according the pull-requests) +ZSNAP="/usr/local/bin/zfSnap.sh" + +# Administrator's email +MAILADMIN="admin1@localhost,admin2@localhost" + +# List of all ZFS snapshot +SNAP_LIST='' +REMOTE_SNAP_LIST='' + +# The prefix to add to the previous snapshot's name which is now useless +PREFIXSNAP='done_' + +###################################################################### +# Functions # +###################################################################### + +# Returns 0 if snapshot exists +SnapExists() { + SNAP_LIST="${SNAP_LIST:-`zfs list -H -o name -t snapshot`}" + local i + for i in $SNAP_LIST; do + [ "$1" = "$i" ] && return 0 + done + return 1 +} + +# Returns 0 if snapshot exists on the remote host +RemoteSnapExist() { + REMOTE_SNAP_LIST="${REMOTE_SNAP_LIST:-`ssh ${1} zfs list -H -o name -t snapshot`}" + local i + for i in $REMOTE_SNAP_LIST; do + [ "$2" = "$i" ] && return 0 + done + return 1 +} + +###################################################################### +# Main logic # +###################################################################### + +interval=$1 + +usage() +{ + if [ "X${interval}" = "X" ]; then + printf 'Usage: %s (async|hourly|daily|weekly|purge)\n' $(basename $0) + printf '\n' + printf '* Asynchronous DR snapshots are kept for 1 hour\n' + printf '* Hourly snapshots are kept for 1 day\n' + printf '* Daily snapshots are kept for one week\n' + printf '* Weekly snapshots are kept for one month\n' + printf '\n' + + exit 1 + fi +} + +if [ ! -f "${LOGFILE}" ]; then + mkdir -p $(dirname ${LOGFILE}) + touch "${LOGFILE}" +fi + +case "${interval}" in + 'async' ) + for dset in $DATASETS + do + # Take snapshots for asynchronouss DR purposes + $ZSNAP -v -s -S -a 1h $dset >> $LOGFILE + + # Get the last snapshot name on localhost + LOCALSNAP=$(zfs list -H -o name -t snapshot -r ${dset} | tail -n1 | cut -d@ -f2) + #printf 'Local snapshot: %s\n' ${LOCALSNAP} + + # Get the last snapshot name on remote host $DHOST + DHOSTSNAP=$(ssh ${DHOST} zfs list -H -o name -t snapshot -r ${dset} | tail -n1 | cut -d@ -f2) + #printf 'Remote snapshot: %s\n' ${DHOSTSNAP} + + # Test if $DHOSTSNAP exist on the local system + # Recompose the full snapshot name with $dset@$DHOSTSNAP + if SnapExists "${dset}@${DHOSTSNAP}"; then + #printf '%s exist on the local system\n' ${DHOSTSNAP} + printf '%s@%s will be send to %s to replace the old snapshot: %s' ${dset} ${LOCALSNAP} ${DHOST} ${DHOSTSNAP} >> $LOGFILE + + # Send the snapshot the remote host + # zfs send -I datastore/vm@2014-11-17_11.26.22--1h datastore/vm@2014-11-18_14.19.10--1h | ssh nec02.ipr.univ-rennes1.fr zfs recv datastore/vm + zfs send -I "${DHOSTSNAP}" "${dset}@${LOCALSNAP}" | ssh "${DHOST}" zfs recv "${dset}" + printf ' ... done\n' >> $LOGFILE + else + # Mail admin + printf 'ERROR snapshot %s does not exist on the local system\n' ${DHOSTSNAP} | mail ${MAILADMIN} -s 'snapsend_error' + printf 'ERROR snapshot %s for %s does not exist on the local system\n' ${DHOSTSNAP} ${dset} >> $LOGFILE + fi + + # Test if the snapshot was successfully receive on the remote host + if RemoteSnapExist "${DHOST}" "${dset}@${LOCALSNAP}"; then + printf 'SUCCESS: the snapshot %s for %s exist on the remote host (%s)\n' ${LOCALSNAP} ${dset} ${DHOST} >> $LOGFILE + + # Rename the useless snapshot for the next purge + zfs rename ${dset}@${DHOSTSNAP} ${dset}@${PREFIXSNAP}${DHOSTSNAP} + else + # Mail admin + printf 'ERROR snapshot %s for %s does not exist on the remote host (%s)\n' ${LOCALSNAP} ${dset} ${DHOST} | mail ${MAILADMIN} -s 'snapsend_error' + printf 'ERROR snapshot %s for %s does not exist on the remote host (%s)\n' ${LOCALSNAP} ${dset} ${DHOST} >> $LOGFILE + fi + done + + printf '\n' >> $LOGFILE + ;; + + 'hourly' ) + # take snapshots, keep for one day + for dset in $DATASETS + do + $ZSNAP -v -s -S -a 1d $dset >> $LOGFILE + done + printf '\n' >> $LOGFILE + ;; + + 'daily' ) + # take snapshots, keep for one week + for dset in $DATASETS + do + $ZSNAP -v -s -S -a 1w $dset >> $LOGFILE + done + + # Purge snapshots according to TTL + $ZSNAP -v -s -S -d -p ${PREFIXSNAP} >> $LOGFILE + printf '\n' >> $LOGFILE + ;; + + 'weekly' ) + # take snapshots, keep for one month + for dset in $DATASETS + do + $ZSNAP -v -s -S -a 1m $dset >> $LOGFILE + done + printf '\n' >> $LOGFILE + ;; + + 'purge' ) + # purge snapshots according to TTL + $ZSNAP -v -s -S -d + ;; + + * ) + usage + ;; + +esac + +exit 0