Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use bookmarks created after the latest snapshot #625

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 43 additions & 24 deletions syncoid
Original file line number Diff line number Diff line change
Expand Up @@ -410,6 +410,7 @@ sub syncdataset {

my $newsyncsnap;
my $matchingsnap;
my $usebookmark = 0;

# skip snapshot checking/creation in case of resumed receive
if (!defined($receivetoken)) {
Expand Down Expand Up @@ -585,24 +586,27 @@ sub syncdataset {
my $targetsize = getzfsvalue($targethost,$targetfs,$targetisroot,'-p used');

my %bookmark = ();
my $bookmarksnap = 0;

$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 (oldest first)
foreach my $snap ( sort { sortsnapshots(\%snaps, $b, $a) } keys %{ $snaps{'target'} }) {
my $guid = $snaps{'target'}{$snap}{'guid'};
# find most recent matching bookmark
my %bookmarks = getbookmarks($sourcehost,$sourcefs,$sourceisroot);

if (defined $bookmarks{$guid}) {
# found a match
%bookmark = %{ $bookmarks{$guid} };
$matchingsnap = $snap;
last;
}
# check for matching guid of source bookmark and target snapshot (oldest first)
foreach my $snap ( sort { sortsnapshots(\%snaps, $b, $a, 'target') } keys %{ $snaps{'target'} }) {
my $guid = $snaps{'target'}{$snap}{'guid'};

if (defined $bookmarks{$guid}) {
# found a match
%bookmark = %{ $bookmarks{$guid} };
$bookmarksnap = $snap;
last;
}
}

if (! $matchingsnap) {
# no matching snapshots, check for bookmarks as fallback
if (! %bookmark) {
# force delete is not possible for the root dataset
if ($args{'force-delete'} && index($targetfs, '/') != -1) {
Expand Down Expand Up @@ -657,6 +661,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 && $bookmark{'guid'} ne $snaps{'source'}{$matchingsnap}{'guid'}) {
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;
}
}

Expand All @@ -676,13 +694,13 @@ 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,
# do an replication to it and replicate as always from oldest to newest
# because bookmark sends doesn't support intermediates directly
foreach my $snap ( sort { sortsnapshots(\%snaps, $a, $b) } keys %{ $snaps{'source'} }) {
foreach my $snap ( sort { sortsnapshots(\%snaps, $a, $b, 'source') } keys %{ $snaps{'source'} }) {
my $comparisonkey = 'creation';
if (defined $snaps{'source'}{$snap}{'createtxg'} && defined $bookmark{'createtxg'}) {
$comparisonkey = 'createtxg';
Expand Down Expand Up @@ -736,7 +754,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
if (!%bookmark || $nextsnapshot) {
if (!$usebookmark || $nextsnapshot) {
if ($matchingsnap eq $newsyncsnap) {
# edge case: bookmark replication used the latest snapshot
return 0;
Expand Down Expand Up @@ -801,7 +819,7 @@ sub syncdataset {

# if "--use-hold" parameter is used set hold on newsync snapshot and remove hold on matching snapshot both on source and target
# hold name: "syncoid" + identifier + hostname -> in case of replication to multiple targets separate holds can be set for each target by assinging different identifiers to each target. Only if all targets have been replicated all syncoid holds are removed from the matching snapshot and it can be removed
if (defined $args{'use-hold'}) {
if (defined $args{'use-hold'} && !$usebookmark) {
my $holdcmd;
my $holdreleasecmd;
my $hostid = hostname();
Expand Down Expand Up @@ -865,7 +883,7 @@ sub syncdataset {
# those that exist on the source. Remaining are the snapshots
# that are only on the target. Then sort to remove the oldest
# snapshots first.
my @to_delete = sort { sortsnapshots(\%snaps, $a, $b) } grep {!exists $snaps{'source'}{$_}} keys %{ $snaps{'target'} };
my @to_delete = sort { sortsnapshots(\%snaps, $a, $b, 'source') } grep {!exists $snaps{'source'}{$_}} keys %{ $snaps{'target'} };
while (@to_delete) {
# Create batch of snapshots to remove
my $snaps = join ',', splice(@to_delete, 0, 50);
Expand Down Expand Up @@ -1486,16 +1504,16 @@ sub readablebytes {
}

sub sortsnapshots {
my ($snaps, $left, $right) = @_;
if (defined $snaps->{'source'}{$left}{'createtxg'} && defined $snaps->{'source'}{$right}{'createtxg'}) {
return $snaps->{'source'}{$left}{'createtxg'} <=> $snaps->{'source'}{$right}{'createtxg'};
my ($snaps, $left, $right, $idx) = @_;
if (defined $snaps->{$idx}{$left}{'createtxg'} && defined $snaps->{$idx}{$right}{'createtxg'}) {
return $snaps->{$idx}{$left}{'createtxg'} <=> $snaps->{$idx}{$right}{'createtxg'};
}
return $snaps->{'source'}{$left}{'creation'} <=> $snaps->{'source'}{$right}{'creation'};
return $snaps->{$idx}{$left}{'creation'} <=> $snaps->{$idx}{$right}{'creation'};
}

sub getoldestsnapshot {
my $snaps = shift;
foreach my $snap (sort { sortsnapshots($snaps, $a, $b) } keys %{ $snaps{'source'} }) {
foreach my $snap (sort { sortsnapshots($snaps, $a, $b, 'source') } keys %{ $snaps{'source'} }) {
# return on first snap found - it's the oldest
return $snap;
}
Expand All @@ -1509,7 +1527,7 @@ sub getoldestsnapshot {

sub getnewestsnapshot {
my $snaps = shift;
foreach my $snap (sort { sortsnapshots($snaps, $b, $a) } keys %{ $snaps{'source'} }) {
foreach my $snap (sort { sortsnapshots($snaps, $b, $a, 'source') } keys %{ $snaps{'source'} }) {
# return on first snap found - it's the newest
writelog('INFO', "NEWEST SNAPSHOT: $snap");
return $snap;
Expand Down Expand Up @@ -1688,7 +1706,7 @@ sub pruneoldsyncsnaps {

sub getmatchingsnapshot {
my ($sourcefs, $targetfs, $snaps) = @_;
foreach my $snap ( sort { sortsnapshots($snaps, $b, $a) } keys %{ $snaps{'source'} }) {
foreach my $snap ( sort { sortsnapshots($snaps, $b, $a, 'source') } keys %{ $snaps{'source'} }) {
if (defined $snaps{'target'}{$snap}) {
if ($snaps{'source'}{$snap}{'guid'} == $snaps{'target'}{$snap}{'guid'}) {
return $snap;
Expand Down Expand Up @@ -1964,6 +1982,7 @@ sub getbookmarks() {

for my $bookmark (keys %bookmark_data) {
my $guid = $bookmark_data{$bookmark}{'guid'};
$bookmarks{$guid}{'guid'} = $guid;
$bookmarks{$guid}{'name'} = $bookmark;
$bookmarks{$guid}{'creation'} = $bookmark_data{$bookmark}{'creation'};
$bookmarks{$guid}{'createtxg'} = $bookmark_data{$bookmark}{'createtxg'};
Expand Down
Original file line number Diff line number Diff line change
@@ -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-11.zpool"
MOUNT_TARGET="/tmp/syncoid-test-11.mount"
POOL_SIZE="1000M"
POOL_NAME="syncoid-test-11"
TARGET_CHECKSUM="9791444505ef5ab4ac8c943cdcbbb99b98fefc0ee658ac048505cc647e25a1f6 -"

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