diff --git a/syncoid b/syncoid index 34e5a12..02ec2e1 100755 --- a/syncoid +++ b/syncoid @@ -440,6 +440,7 @@ sub syncdataset { my $newsyncsnap; my $matchingsnap; + my $usebookmark = 0; # skip snapshot checking/creation in case of resumed receive if (!defined($receivetoken)) { @@ -606,24 +607,26 @@ sub syncdataset { my $targetsize = getzfsvalue($targethost,$targetfs,$targetisroot,'-p used'); my %bookmark = (); + my $bookmarksnap = 0; + + # find most recent matching bookmark + my %bookmarks = getbookmarks($sourcehost,$sourcefs,$sourceisroot); + + # check for matching guid of source bookmark and target snapshot (newest first) + foreach my $snap ( sort { sortsnapshots($snaps{'target'}, $b, $a) } keys %{ $snaps{'target'} }) { + my $guid = $snaps{'target'}{$snap}{'guid'}; + + if (defined $bookmarks{$guid}) { + # found a match + %bookmark = %{ $bookmarks{$guid} }; + $bookmarksnap = $snap; + last; + } + } $matchingsnap = getmatchingsnapshot($sourcefs, $targetfs, \%snaps); if (! $matchingsnap) { # no matching snapshots, check for bookmarks as fallback - my %bookmarks = getbookmarks($sourcehost,$sourcefs,$sourceisroot); - - # check for matching guid of source bookmark and target snapshot (newest first) - foreach my $snap ( sort { sortsnapshots($snaps{'target'}, $b, $a) } keys %{ $snaps{'target'} }) { - my $guid = $snaps{'target'}{$snap}{'guid'}; - - if (defined $bookmarks{$guid}) { - # found a match - %bookmark = %{ $bookmarks{$guid} }; - $matchingsnap = $snap; - last; - } - } - if (! %bookmark) { # force delete is not possible for the root dataset if ($args{'force-delete'} && index($targetfs, '/') != -1) { @@ -678,6 +681,20 @@ sub syncdataset { # return false now in case more child datasets need replication. return 0; + } else { + # use bookmark as fallback + $matchingsnap = $bookmarksnap; + $usebookmark = 1; + } + } elsif (%bookmark) { + my $comparisonkey = 'creation'; + if (defined $snaps{'source'}{$matchingsnap}{'createtxg'} && defined $bookmark{'createtxg'}) { + $comparisonkey = 'createtxg'; + } + if ($bookmark{$comparisonkey} > $snaps{'source'}{$matchingsnap}{$comparisonkey}) { + writelog('DEBUG', "using bookmark $bookmark{'name'} because it was created after latest matching snapshot $matchingsnap"); + $matchingsnap = $bookmarksnap; + $usebookmark = 1; } } @@ -697,7 +714,7 @@ sub syncdataset { my $nextsnapshot = 0; - if (%bookmark) { + if ($usebookmark) { if (!defined $args{'no-stream'}) { # if intermediate snapshots are needed we need to find the next oldest snapshot, @@ -758,7 +775,7 @@ sub syncdataset { # do a normal replication if bookmarks aren't used or if previous # bookmark replication was only done to the next oldest snapshot # edge case: skip replication if bookmark replication used the latest snapshot - if ((!%bookmark || $nextsnapshot) && !($matchingsnap eq $newsyncsnap)) { + if ((!$usebookmark || $nextsnapshot) && !($matchingsnap eq $newsyncsnap)) { ($exit, $stdout) = syncincremental($sourcehost, $sourcefs, $targethost, $targetfs, $matchingsnap, $newsyncsnap, defined($args{'no-stream'})); diff --git a/tests/syncoid/015_use_bookmarks_created_after_latest_snapshot/run.sh b/tests/syncoid/015_use_bookmarks_created_after_latest_snapshot/run.sh new file mode 100755 index 0000000..5babee8 --- /dev/null +++ b/tests/syncoid/015_use_bookmarks_created_after_latest_snapshot/run.sh @@ -0,0 +1,62 @@ +#!/bin/bash + +# test using bookmark created after last snapshot + +set -x +set -e + +. ../../common/lib.sh + +POOL_IMAGE="/tmp/syncoid-test-015.zpool" +MOUNT_TARGET="/tmp/syncoid-test-015.mount" +POOL_SIZE="1000M" +POOL_NAME="syncoid-test-015" +TARGET_CHECKSUM="73d7271f58f0d79eea0dd69d5ee3f4fe3aeaa3cb8106f7fc88feded5be3ce04e -" + +truncate -s "${POOL_SIZE}" "${POOL_IMAGE}" + +zpool create -m "${MOUNT_TARGET}" -f "${POOL_NAME}" "${POOL_IMAGE}" + +function cleanUp { + zpool export "${POOL_NAME}" +} + +# export pool in any case +trap cleanUp EXIT + +zfs create "${POOL_NAME}"/a +zfs snapshot "${POOL_NAME}"/a@s0 + +# This fully replicates a to b +../../../syncoid --debug --no-sync-snap --no-rollback --create-bookmark "${POOL_NAME}"/a "${POOL_NAME}"/b + +echo "Test 1" > "${MOUNT_TARGET}"/a/file1 +zfs snapshot "${POOL_NAME}"/a@s1 + +# This incrementally replicates from a@s0 to a@s1 +../../../syncoid --debug --no-sync-snap --no-rollback --create-bookmark "${POOL_NAME}"/a "${POOL_NAME}"/b + +echo "Test 2" > "${MOUNT_TARGET}"/a/file2 +zfs snapshot "${POOL_NAME}"/a@s2 + +# Destroy latest common snap between a and b +zfs destroy "${POOL_NAME}"/a@s1 + +# This uses a#s1 as base snap although common but older snap a@s0 exists +../../../syncoid --debug --no-sync-snap --no-rollback --create-bookmark "${POOL_NAME}"/a "${POOL_NAME}"/b + +echo "Test 3" > "${MOUNT_TARGET}"/a/file3 +zfs snapshot "${POOL_NAME}"/a@s3 + +# This uses a@s2 as base snap again +../../../syncoid --debug --no-sync-snap --no-rollback --create-bookmark "${POOL_NAME}"/a "${POOL_NAME}"/b + +# verify +output=$(zfs list -t snapshot -r -H -o name "${POOL_NAME}") +checksum=$(echo "${output}" | shasum -a 256) + +if [ "${checksum}" != "${TARGET_CHECKSUM}" ]; then + exit 1 +fi + +exit 0