Initial working draft of the new replicate algorithm using
authorAlan J. Pippin <ajp@pippins.net>
Tue, 13 Jan 2009 07:56:18 +0000 (00:56 -0700)
committerAlan J. Pippin <ajp@pippins.net>
Tue, 13 Jan 2009 07:56:18 +0000 (00:56 -0700)
backup marker snapshots instead of pure snapshot diffs.

zfs-replicate

index 7c6e65027f64f40f6af4bc56860bda74a688bfc1..128d051a3489903a0e1d84cc67a76ed5ed61e933 100755 (executable)
@@ -1,14 +1,48 @@
 #/bin/bash
 
-set -x
-
 # Usage: replicate <hostname> <zfs filesystem>
 remote=$1
 remote_fs=$2
+remote_pool=${2%%/*}
 
-# change to match the name of the local backup pool
+# Set this variable to '1' to use the legacy, non-marker, snapshot diff, replicate script logic
+use_legacy_replicate=0
+
+# Set the name of the local pool used to store the backup of the remote
 local_pool=backups
 
+# Make sure we have valid arguments
+if [[ -z "$remote" ]] || [[ -z "$remote_fs" ]]; then
+  echo "Usage: $0 <hostname> <zfs filesystem>"
+  exit 1
+fi
+
+# Make sure the local pool and local receiving filesystem exist, or print some errors
+if ! zpool list -H "$local_pool" >/dev/null 2>&1; then
+  echo >&2 "-E- The local pool, '$local_pool' doesn't seem to exist."
+  exit 1
+fi
+if ! zfs list "$local_pool/$remote_pool" >/dev/null 2>&1; then
+  echo >&2 "-E- The local filesystem for the remote pool, '$local_pool/$remote_pool' doesn't seem to exist."
+  echo >&2 "    You will need to create this filesystem before this script can replicate your data."
+  echo >&2 "    You can create this filsystem by executing this command: 'zfs create $local_pool/$remote_pool'"
+  exit 1
+fi
+
+# Obtain the zpool guid for the local pool
+local_pool_guid=`zpool get guid $local_pool 2>&1 | grep $local_pool | awk '{ print $3 }'`
+if ! zpool get guid $local_pool > /dev/null 2>&1; then
+  echo >&2 "-E- Unable to extract the guid for the local pool: $local_pool"
+  exit 1
+fi
+
+# Turn on shell verbosity
+set -x
+
+# Setup our backup marker names
+current_backup_marker=${remote_fs}@current-backup-${local_pool_guid}
+previous_backup_marker=${remote_fs}@previous-backup-${local_pool_guid}
+
 # The ssh connection doesn't find zfs without this.
 zfs=/usr/sbin/zfs
 
@@ -18,29 +52,101 @@ ssh $remote \
     $zfs list -H -t snapshot |
     grep ^${remote_fs}@ |
     awk '{print$1}' > $remote_list
+if [[ $? != 0 ]]; then
+  echo "-E- remote $zfs list command failed"
+  exit 1
+fi
 
 # List the snapshots on the local machine.
 local_list=$(mktemp /tmp/replicate.XXXXXX)
 $zfs list -H -t snapshot |
     grep ^${local_pool}/${remote_fs}@ |
     awk '{gsub(/^${local_pool}./,"",$1); print$1}' > $local_list
+if [[ $? != 0 ]]; then
+  echo "-E- local $zfs list command failed"
+  exit 1
+fi
+
+if [ $use_legacy_replicate == 0 ]; then
+  # Destroy the current backup marker snapshot on the remote system if it exists
+  grep -q ${current_backup_marker} $remote_list
+  if [ $? == 0 ]; then
+    ssh $remote $zfs destroy ${current_backup_marker} 
+    if [[ $? != 0 ]]; then
+      echo "-E- remote $zfs destroy command failed"
+      exit 1
+    fi
+  fi
+  # Create the current backup marker snapshot on the remote system
+  ssh $remote $zfs snapshot ${current_backup_marker}
+  if [[ $? != 0 ]]; then
+    echo "-E- remote $zfs snapshot command failed"
+    exit 1
+  fi
 
-# See what the most recent snapshot on the remote end is.
-latest=$(tail -n 1 $remote_list)
+  # Check to see if the previous backup marker exists in the remote snapshot list.
+  # Check to see if the previous backup marker exists in the local snapshot list.
+  # If the previous backup markers exists, perform an incremental replicate.
+  # Otherwise, perform a full replicate.
+  grep -q ${previous_backup_marker} $remote_list
+  full=$?
+  grep -q ${previous_backup_marker} $local_list
+  full=$(($full || $?))
 
-# I did this to make sure that diff would always display the most recent common
-echo bogus.remote >> $remote_list
-echo bogus.local  >> $local_list
-common=$(diff -u $remote_list $local_list | grep '^ ' | tail -n 1)
+  if [[ $full == 0 ]]; then
+    ssh $remote $zfs send -R -I${previous_backup_marker} ${current_backup_marker} | 
+        $zfs receive -vF -d ${local_pool}/${remote_fs%/*}
+    if [[ $? != 0 ]]; then
+      echo "-E- remote incremental $zfs send command failed"
+      exit 1
+    fi
+  else
+    ssh $remote $zfs send -R ${current_backup_marker} |
+        $zfs receive -vF -d ${local_pool}/${remote_fs%/*}
+    if [[ $? != 0 ]]; then
+      echo "-E- remote full $zfs send command failed"
+      exit 1
+    fi
+  fi
+  # destroy the previous backup markers now that we've replicated past them
+  $zfs destroy ${local_pool}/${previous_backup_marker} > /dev/null 2>&1
+  ssh $remote $zfs destroy ${previous_backup_marker} > /dev/null 2>&1
 
-if [ -n "$common" ]; then 
-  # We found a common snapshot
-  ssh $remote $zfs send -R -I${common/*@/@} $latest |
-      $zfs receive -vF -d ${local_pool}/${remote_fs%/*}
+  # Rename the current backup marker to be the previous backup marker
+  $zfs rename ${local_pool}/${current_backup_marker} ${local_pool}/${previous_backup_marker}
+  if [[ $? != 0 ]]; then
+    echo "-E- local $zfs rename command failed"
+    exit 1
+  fi
+  ssh $remote $zfs rename ${current_backup_marker} ${previous_backup_marker}
+  if [[ $? != 0 ]]; then
+    echo "-E- remote $zfs rename command failed"
+    exit 1
+  fi
+   
 else
-  # We did not find a common snapshot, so send the entire filesystem
-  ssh $remote $zfs send -R $latest |
-      $zfs receive -vF -d ${local_pool}/${remote_fs%/*}
-endif
+  # See what the most recent snapshot on the remote end is.
+  latest=$(tail -n 1 $remote_list)
+
+  # I did this to make sure that diff would always display the most recent common
+  # Since we're keying off the context of the diff, we need to ensure we will get context
+  # by injecting a known difference in case no others exist in the lists.
+  echo bogus.remote >> $remote_list
+  echo bogus.local  >> $local_list
+  common=$(diff -u $remote_list $local_list | grep '^ ' | tail -n 1)
+
+  if [ -n "$common" ]; then 
+    # We found a common snapshot
+    ssh $remote $zfs send -R -I${common/*@/@} $latest |
+        $zfs receive -vF -d ${local_pool}/${remote_fs%/*}
+  else
+    # We did not find a common snapshot, so send the entire filesystem
+    ssh $remote $zfs send -R $latest |
+        $zfs receive -vF -d ${local_pool}/${remote_fs%/*}
+  fi
+fi
+
+# Remove tmp files
+#rm -f $local_list $remote_list
 
-rm -f $local_list $remote_list