Use bookmarks created after the latest snapshot

This commit changes syncoid's behavior so it is always looking for
a matching snapshot and a matching bookmark. If the bookmark was created
after the snapshot it is used instead. This allows replication when the
latest snapshot replicated was deleted on the source, a common snapshot
was found but rollback on the target is not allowed. The matching bookmark
is used instead for replication.

This fixes https://github.com/jimsalterjrs/sanoid/issues/602

Signed-off-by: Felix Matouschek <felix@matouschek.org>
This commit is contained in:
Felix Matouschek 2021-03-14 17:45:52 +01:00 committed by Felix Matouschek
parent 8e1d11e0b2
commit e49d52de4a
2 changed files with 95 additions and 16 deletions

49
syncoid
View File

@ -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'}));

View File

@ -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