implemented sync mode

This commit is contained in:
Tamás Gérczei 2020-04-26 10:46:05 +02:00
parent 8d823a95d3
commit 8ce8ceff3f
1 changed files with 93 additions and 70 deletions

View File

@ -22,18 +22,18 @@ function snapuse() {
function human() { function human() {
# found at http://unix.stackexchange.com/a/191787 # found at http://unix.stackexchange.com/a/191787
nawk 'function human(x) { nawk 'function human(x) {
x[1]/=1024; x[1]/=1024;
if (x[1]>=1000) { x[2]++; human(x); } if (x[1]>=1000) { x[2]++; human(x); }
} }
{a[1]=$1; a[2]=0; human(a); printf "%.2f%s\n",a[1],substr("KMGTEPYZ",a[2]+1,1)}' <<< $1 {a[1]=$1; a[2]=0; human(a); printf "%.2f%s\n",a[1],substr("KMGTEPYZ",a[2]+1,1)}' <<< $1
} }
function backup() { function backup() {
# check source # check source
check_dataset ${DATASET} || { check_dataset ${DATASET} || {
logger -t $(basename ${0%.sh}) -p user.notice "source dataset \"${DATASET}\" in configuration entry #${COUNTER} does not exist; omitting" logger -t $(basename ${0%.sh}) -p user.notice "source dataset \"${DATASET}\" in configuration entry #${COUNTER} does not exist; omitting"
continue 2 continue 2
} }
# determine which local snapshots exist already # determine which local snapshots exist already
SNAPSHOTS=( $(zfs list -rt snapshot -d1 -Ho name -S creation ${DATASET} 2>/dev/null) ) SNAPSHOTS=( $(zfs list -rt snapshot -d1 -Ho name -S creation ${DATASET} 2>/dev/null) )
@ -69,21 +69,24 @@ function backup() {
# check target # check target
check_dataset ${SAVETO} || { check_dataset ${SAVETO} || {
logger -t $(basename ${0%.sh}) -p user.notice "target dataset \"${SAVETO}\" in configuration entry #${COUNTER} does not exist; omitting" logger -t $(basename ${0%.sh}) -p user.notice "target dataset \"${SAVETO}\" in configuration entry #${COUNTER} does not exist; omitting"
continue 2 continue 2
} }
R_SNAPSHOTS=( $(${R_RMOD} zfs list -rt snapshot -d1 -Ho name -S creation ${SAVETO}/$( basename ${DATASET}) 2>/dev/null) ) R_SNAPSHOTS=( $(${R_RMOD} zfs list -rt snapshot -d1 -Ho name -S creation ${SAVETO}/$( basename ${DATASET}) 2>/dev/null) )
check_dataset ${SAVETO}/$( basename ${DATASET}) && R_USED_BEFORE=$(snapuse ${SAVETO}/$( basename ${DATASET})) check_dataset ${SAVETO}/$( basename ${DATASET}) && R_USED_BEFORE=$(snapuse ${SAVETO}/$( basename ${DATASET}))
# determine current timestamp if [ ${MODE:-backup} == "backup" ]
DATE=$(date +%Y-%m-%d-%H%M) then
# determine current timestamp
DATE=$(date +%Y-%m-%d-%H%M)
# determine the name of the current snapshot to create # determine the name of the current snapshot to create
NEWSNAP="${DATASET}@${DATE}" NEWSNAP="${DATASET}@${DATE}"
# take a snapshot # take a snapshot
zfs snapshot -r ${NEWSNAP} zfs snapshot -r ${NEWSNAP}
fi
# check if source is encrypted # check if source is encrypted
if [ $ENCRYPTION_FEATURE != "disabled" ] if [ $ENCRYPTION_FEATURE != "disabled" ]
@ -103,40 +106,50 @@ function backup() {
# local snapshot(s) found # local snapshot(s) found
SNAPMODIFIER="i ${LASTSNAP}" SNAPMODIFIER="i ${LASTSNAP}"
check_dataset ${SAVETO}/$(basename ${LASTSNAP}) || { check_dataset ${SAVETO}/$(basename ${LASTSNAP}) || {
# last local snapshot is not available at the destination location # last local snapshot is not available at the destination location
if [ ${#R_SNAPSHOTS[*]} -ge 1 ] if [ ${#R_SNAPSHOTS[*]} -ge 1 ]
then then
# remote snapshot(s) found # remote snapshot(s) found
R_SNAPMODIFIER="I $(dirname ${DATASET})/$(basename ${R_SNAPSHOTS[*]:(-1)})" R_SNAPMODIFIER="I $(dirname ${DATASET})/$(basename ${R_SNAPSHOTS[0]})"
fi fi
# send any previous snapshots # send any previous snapshots
zfs send -Rcv${RAW_MOD}${R_SNAPMODIFIER} ${LASTSNAP} | ${RMOD:-$R_RMOD} zfs recv -Feu${RESUME_MOD}v ${SAVETO} 2>&1 >> ${LOGFILE} zfs send -Rcv${RAW_MOD}${R_SNAPMODIFIER} ${LASTSNAP} | ${RMOD:-$R_RMOD} zfs recv -Feu${RESUME_MOD}v ${SAVETO} 2>&1 >> ${LOGFILE}
} if [[ ${PIPESTATUS[*]} =~ [1-9]+ ]]
then
# replication failure
logger -t $(basename ${0%.sh}) -p user.notice "failed to replicate ${LASTSNAP} to ${TARGET:-local}:${SAVETO}"
else
let CHANGES++
fi
}
else else
# ensure this does not remain in effect # ensure this does not remain in effect
unset SNAPMODIFIER R_SNAPMODIFIER unset SNAPMODIFIER R_SNAPMODIFIER
fi fi
# send backup if [ ${MODE:-backup} == "backup" ]
zfs send -Rcv${RAW_MOD}${SNAPMODIFIER} ${NEWSNAP} | ${RMOD:-$R_RMOD} zfs recv -Feu${RESUME_MOD}v ${SAVETO} 2>&1 >> ${LOGFILE}
# if replication is unsuccessful, omit the aging check so as to prevent data loss
if [ $? -eq 0 ]
then then
THRESHOLD=$(( $KEEP * 24 * 3600 )) # send backup
for SNAPSHOT in ${SNAPSHOTS[*]} zfs send -Rcv${RAW_MOD}${SNAPMODIFIER} ${NEWSNAP} | ${RMOD:-$R_RMOD} zfs recv -Feu${RESUME_MOD}v ${SAVETO} 2>&1 >> ${LOGFILE}
do
TIMESTAMP=$(zfs get -pHo value creation "${SNAPSHOT}") # if replication is unsuccessful, omit the aging check so as to prevent data loss
AGE=$(( $NOW - $TIMESTAMP )) if [[ ! ${PIPESTATUS[*]} =~ [1-9]+ ]]
if [ $AGE -ge $THRESHOLD ] then
then let CHANGES++
zfs destroy -r ${SNAPSHOT} 2>&1 >> ${LOGFILE} THRESHOLD=$(( $KEEP * 24 * 3600 ))
${RMOD:-$R_RMOD} zfs destroy -r ${SAVETO}/$(basename ${SNAPSHOT}) 2>&1 >> ${LOGFILE} for SNAPSHOT in ${SNAPSHOTS[*]}
fi do
done TIMESTAMP=$(zfs get -pHo value creation "${SNAPSHOT}")
else AGE=$(( $NOW - $TIMESTAMP ))
MY_EXIT_CODE=$? if [ $AGE -ge $THRESHOLD ]
logger -t $(basename ${0%.sh}) -p user.notice "failed to replicate ${NEWSNAP} (rc: $MY_EXIT_CODE) to ${TARGET:-local}:${SAVETO}, no aging" then
zfs destroy -r ${SNAPSHOT} 2>&1 >> ${LOGFILE}
${RMOD:-$R_RMOD} zfs destroy -r ${SAVETO}/$(basename ${SNAPSHOT}) 2>&1 >> ${LOGFILE}
fi
done
else
logger -t $(basename ${0%.sh}) -p user.notice "failed to replicate ${NEWSNAP} to ${TARGET:-local}:${SAVETO}, no aging"
fi
fi fi
# re-evaluate snapshot data usage # re-evaluate snapshot data usage
@ -148,34 +161,38 @@ function backup() {
R_RMOD="ssh ${USER}@${TARGET}" R_RMOD="ssh ${USER}@${TARGET}"
fi fi
R_USED_AFTER=$(snapuse ${SAVETO}/$( basename ${DATASET})) if [ ${CHANGES:-0} -gt 0 ]
L_DELTA=$(( $L_USED_AFTER - $L_USED_BEFORE )) then
R_DELTA=$(( $R_USED_AFTER - ${R_USED_BEFORE:-0} )) # calculate data volume change
R_USED_AFTER=$(snapuse ${SAVETO}/$( basename ${DATASET}))
L_DELTA=$(( $L_USED_AFTER - $L_USED_BEFORE ))
R_DELTA=$(( $R_USED_AFTER - ${R_USED_BEFORE:-0} ))
for DELTA in L_DELTA R_DELTA for DELTA in L_DELTA R_DELTA
do do
unset WHAT WHERE unset WHAT WHERE
eval _DELTA=\$$DELTA eval _DELTA=\$$DELTA
if [ $_DELTA -lt 0 ] if [ $_DELTA -lt 0 ]
then then
_DELTA=$(( $_DELTA * -1 )) _DELTA=$(( $_DELTA * -1 ))
WHAT="freed" WHAT="freed"
fi fi
if [[ $DELTA =~ ^L ]] if [[ $DELTA =~ ^L ]]
then then
WHERE="$(dirname ${DATASET})" WHERE="$(dirname ${DATASET})"
else else
WHERE="${SAVETO}" WHERE="${SAVETO}"
fi fi
if [ $_DELTA -ne 0 ] if [ $_DELTA -ne 0 ]
then then
_DELTA=$(human $_DELTA) _DELTA=$(human $_DELTA)
logger -t $(basename ${0%.sh}) -p user.notice "$_DELTA ${WHAT:-allocated} in \"${WHERE}\" by backing up \"${DATASET}\"" logger -t $(basename ${0%.sh}) -p user.notice "$_DELTA ${WHAT:-allocated} in \"${WHERE}\" by backing up \"${DATASET}\""
fi fi
done done
fi
# reset remote configuration # reset remote configuration
unset R_RMOD RMOD RAW_MOD unset R_RMOD RMOD RAW_MOD
@ -185,7 +202,7 @@ function backup() {
#### BEGIN LOGIC #### #### BEGIN LOGIC ####
while getopts hf:m: OPTION while getopts hf:m:s OPTION
do do
case "$OPTION" in case "$OPTION" in
f) f)
@ -198,6 +215,11 @@ while getopts hf:m: OPTION
RECIPIENT="$OPTARG" RECIPIENT="$OPTARG"
;; ;;
s)
# sync mode | this could also be a per-target setting to mitigate the need to call multiple jobs
MODE="sync"
;;
h|\?) h|\?)
# display help # display help
usage usage
@ -280,6 +302,7 @@ while read -u 4 DATASET SAVETO KEEP ENABLED
let COUNTER++ let COUNTER++
if [ ${ENABLED} == "Y" ] if [ ${ENABLED} == "Y" ]
then then
# split this function -> snapshot(), sync(), age() ? -> a new backup() would call all three
backup backup
fi fi
done 4<<< "$(egrep -v '^(#|$)' "${CFGFILE}")" # graciously overlook any comments or blank lines done 4<<< "$(egrep -v '^(#|$)' "${CFGFILE}")" # graciously overlook any comments or blank lines