From 309c0866fa8dc7793846e607015cc627dcd98350 Mon Sep 17 00:00:00 2001 From: Christoph Klaffl Date: Mon, 7 Dec 2020 22:40:28 +0100 Subject: [PATCH] implemented removal of conflicting snapshots with force-delete option --- README.md | 4 +- syncoid | 32 ++++++++++++- tests/syncoid/8_force_delete_snapshot/run.sh | 48 ++++++++++++++++++++ 3 files changed, 81 insertions(+), 3 deletions(-) create mode 100755 tests/syncoid/8_force_delete_snapshot/run.sh diff --git a/README.md b/README.md index b4558c0..5a052e9 100644 --- a/README.md +++ b/README.md @@ -336,11 +336,11 @@ As of 1.4.18, syncoid also automatically supports and enables resume of interrup + --force-delete - Remove target datasets recursively (WARNING: this will also affect child datasets with matching snapshots/bookmarks), if there are no matching snapshots/bookmarks. + Remove target datasets recursively (WARNING: this will also affect child datasets with matching snapshots/bookmarks), if there are no matching snapshots/bookmarks. Also removes conflicting snapshots if the replication would fail because of a snapshot which has the same name between source and target but different contents. + --no-clone-handling - This argument tells syncoid to not recreate clones on the targe on initial sync and doing a normal replication instead. + This argument tells syncoid to not recreate clones on the target on initial sync and doing a normal replication instead. + --dumpsnaps diff --git a/syncoid b/syncoid index b771d8f..7d8329b 100755 --- a/syncoid +++ b/syncoid @@ -813,6 +813,36 @@ sub syncdataset { if ($exitcode < 2) { $exitcode = 2; } return 0; } + } elsif ($args{'force-delete'} && $stdout =~ /\Qdestination already exists\E/) { + (my $existing) = $stdout =~ m/^cannot restore to ([^:]*): destination already exists$/g; + if ($existing eq "") { + warn "CRITICAL ERROR: $synccmd failed: $?"; + if ($exitcode < 2) { $exitcode = 2; } + return 0; + } + + if (!$quiet) { print "WARN: removing existing destination: $existing\n"; } + my $rcommand = ''; + my $mysudocmd = ''; + my $existingescaped = escapeshellparam($existing); + + if ($targethost ne '') { $rcommand = "$sshcmd $targethost"; } + if (!$targetisroot) { $mysudocmd = $sudocmd; } + + my $prunecmd = "$mysudocmd $zfscmd destroy $existingescaped; "; + if ($targethost ne '') { + $prunecmd = escapeshellparam($prunecmd); + } + + my $ret = system("$rcommand $prunecmd"); + if ($ret != 0) { + warn "CRITICAL ERROR: $rcommand $prunecmd failed: $?"; + if ($exitcode < 2) { $exitcode = 2; } + return 0; + } else { + # redo sync and skip snapshot creation (already taken) + return syncdataset($sourcehost, $sourcefs, $targethost, $targetfs, undef, 1); + } } else { warn "CRITICAL ERROR: $synccmd failed: $?"; if ($exitcode < 2) { $exitcode = 2; } @@ -1994,4 +2024,4 @@ Options: --no-clone-handling Don't try to recreate clones on target --no-privilege-elevation Bypass the root check, for use with ZFS permission delegation - --force-delete Remove target datasets recursively, if there are no matching snapshots/bookmarks + --force-delete Remove target datasets recursively, if there are no matching snapshots/bookmarks (also overwrites conflicting named snapshots) diff --git a/tests/syncoid/8_force_delete_snapshot/run.sh b/tests/syncoid/8_force_delete_snapshot/run.sh new file mode 100755 index 0000000..899092a --- /dev/null +++ b/tests/syncoid/8_force_delete_snapshot/run.sh @@ -0,0 +1,48 @@ +#!/bin/bash + +# test replication with deletion of conflicting snapshot on target + +set -x +set -e + +. ../../common/lib.sh + +POOL_IMAGE="/tmp/syncoid-test-8.zpool" +POOL_SIZE="200M" +POOL_NAME="syncoid-test-8" +TARGET_CHECKSUM="ee439200c9fa54fc33ce301ef64d4240a6c5587766bfeb651c5cf358e11ec89d -" + +truncate -s "${POOL_SIZE}" "${POOL_IMAGE}" + +zpool create -m none -f "${POOL_NAME}" "${POOL_IMAGE}" + +function cleanUp { + zpool export "${POOL_NAME}" +} + +# export pool in any case +trap cleanUp EXIT + +zfs create "${POOL_NAME}"/src +zfs snapshot "${POOL_NAME}"/src@duplicate + +# initial replication +../../../syncoid -r --debug --compress=none "${POOL_NAME}"/src "${POOL_NAME}"/dst +# recreate snapshot with the same name on src +zfs destroy "${POOL_NAME}"/src@duplicate +zfs snapshot "${POOL_NAME}"/src@duplicate +sleep 1 +../../../syncoid -r --force-delete --debug --compress=none "${POOL_NAME}"/src "${POOL_NAME}"/dst || exit 1 + +# verify +output1=$(zfs list -t snapshot -r -H -o guid,name "${POOL_NAME}"/src | sed 's/@syncoid_.*$'/@syncoid_/) +checksum1=$(echo "${output1}" | shasum -a 256) + +output2=$(zfs list -t snapshot -r -H -o guid,name "${POOL_NAME}"/dst | sed 's/@syncoid_.*$'/@syncoid_/ | sed 's/dst/src/') +checksum2=$(echo "${output2}" | shasum -a 256) + +if [ "${checksum1}" != "${checksum2}" ]; then + exit 1 +fi + +exit 0