From d3f674fd2eebfa2a1127d64aa7588f3c6c677654 Mon Sep 17 00:00:00 2001 From: Tiger Kaovilai Date: Sat, 24 Oct 2026 20:22:51 -0400 Subject: [PATCH] Treat a kopia snapshot not found after a repo refresh as deleted Deleting a kopia snapshot refreshes the repo when the snapshot is not found, and failed when it was still not found. When a backup deletion was interrupted after the snapshot was deleted, e.g. by a Velero server restart, every retry of the deletion failed with "snapshot not found", so the backup could never be deleted. A snapshot that is still not found after the refresh is already deleted, so return success. Signed-off-by: Tiger Kaovilai --- pkg/repository/udmrepo/kopialib/lib_repo.go | 8 ++++++++ pkg/repository/udmrepo/kopialib/lib_repo_test.go | 15 +++++++++++++-- 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/pkg/repository/udmrepo/kopialib/lib_repo.go b/pkg/repository/udmrepo/kopialib/lib_repo.go index 2290c57f6..94729ddbc 100644 --- a/pkg/repository/udmrepo/kopialib/lib_repo.go +++ b/pkg/repository/udmrepo/kopialib/lib_repo.go @@ -745,6 +745,14 @@ func (kr *kopiaRepository) DeleteSnapshot(ctx context.Context, id udmrepo.ID) er _, err = kr.GetSnapshot(ctx, id) if err != nil { + // The snapshot was already deleted, e.g. by a backup deletion that was + // interrupted after deleting it, so there is nothing left to do. + if errors.Is(err, snapshot.ErrSnapshotNotFound) { + if kr.logger != nil { + kr.logger.Infof("Snapshot %s is not found after repo refresh, it is already deleted", id) + } + return nil + } return errors.Wrap(err, "error getting snapshot after repo refresh") } } diff --git a/pkg/repository/udmrepo/kopialib/lib_repo_test.go b/pkg/repository/udmrepo/kopialib/lib_repo_test.go index af704ca2e..519ac916b 100644 --- a/pkg/repository/udmrepo/kopialib/lib_repo_test.go +++ b/pkg/repository/udmrepo/kopialib/lib_repo_test.go @@ -1719,14 +1719,25 @@ func TestDeleteSnapshot(t *testing.T) { expectedErr: "error refreshing repo on snapshot not found: fake-refresh-error", }, { - name: "snapshot not found in cache, refreshed, but still not found", + name: "snapshot not found in cache, refreshed, but still not found, so it is already deleted", snapshotID: udmrepo.ID("fake-id"), mockRepo: func(r *repomocks.MockRepository) { r.On("GetManifest", mock.Anything, manifest.ID("fake-id"), mock.Anything).Return(nil, manifest.ErrNotFound).Once() r.On("Refresh", mock.Anything).Return(nil).Once() r.On("GetManifest", mock.Anything, manifest.ID("fake-id"), mock.Anything).Return(nil, manifest.ErrNotFound).Once() }, - expectedErr: "error getting snapshot after repo refresh: error getting snapshot manifest: snapshot not found", + }, + { + name: "snapshot not found in cache, refreshed, then get snapshot fail", + snapshotID: udmrepo.ID("fake-id"), + mockRepo: func(r *repomocks.MockRepository) { + r.On("GetManifest", mock.Anything, manifest.ID("fake-id"), mock.Anything).Return(nil, manifest.ErrNotFound).Once() + r.On("Refresh", mock.Anything).Return(nil).Once() + r.On("GetManifest", mock.Anything, manifest.ID("fake-id"), mock.Anything).Return(&manifest.EntryMetadata{ + Labels: map[string]string{manifest.TypeLabelKey: snapshot.ManifestType}, + }, errors.New("fake-get-error")).Once() + }, + expectedErr: "error getting snapshot after repo refresh: error getting snapshot manifest: unable to find manifest entries: fake-get-error", }, { name: "snapshot not found in cache, refreshed, found, and delete succeed",