Skip to content

Commit 06ffb2d

Browse files
authored
K8SPSMDB-801: make delete-backup delete PITR files (#1279)
* K8SPSMDB-801: make `delete-backup` delete PITR files https://jira.percona.com/browse/K8SPSMDB-801 * update comments
1 parent 3d4ed9c commit 06ffb2d

File tree

3 files changed

+115
-52
lines changed

3 files changed

+115
-52
lines changed

e2e-tests/pitr/conf/backup-minio.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ apiVersion: psmdb.percona.com/v1
22
kind: PerconaServerMongoDBBackup
33
metadata:
44
name:
5+
finalizers:
6+
- delete-backup
57
spec:
68
clusterName: some-name
79
storageName: minio

e2e-tests/pitr/run

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,11 @@ main() {
148148

149149
check_recovery "${backup_name_minio}-3" latest "" "-4th" backupSource "${cluster/-rs0/}"
150150

151+
kubectl patch psmdb "some-name" --type='merge' --patch '{"spec": {"backup": {"pitr": {"enabled": false}}}}'
152+
sleep 20
153+
154+
kubectl_bin delete psmdb-backup --all
155+
151156
destroy $namespace
152157

153158
desc 'test passed'

pkg/controller/perconaservermongodbbackup/perconaservermongodbbackup_controller.go

Lines changed: 108 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -5,17 +5,17 @@ import (
55
"fmt"
66
"time"
77

8+
"github.com/percona/percona-backup-mongodb/pbm"
9+
pbmLog "github.com/percona/percona-backup-mongodb/pbm/log"
810
"github.com/percona/percona-backup-mongodb/pbm/storage"
911
"github.com/percona/percona-backup-mongodb/pbm/storage/azure"
1012
"github.com/percona/percona-backup-mongodb/pbm/storage/s3"
11-
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
12-
13-
"github.com/percona/percona-backup-mongodb/pbm"
14-
"go.mongodb.org/mongo-driver/bson/primitive"
15-
1613
"github.com/pkg/errors"
14+
"go.mongodb.org/mongo-driver/bson"
15+
"go.mongodb.org/mongo-driver/bson/primitive"
1716
corev1 "k8s.io/api/core/v1"
1817
k8serrors "k8s.io/apimachinery/pkg/api/errors"
18+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
1919
"k8s.io/apimachinery/pkg/runtime"
2020
"k8s.io/apimachinery/pkg/types"
2121
"k8s.io/client-go/util/retry"
@@ -317,53 +317,7 @@ func (r *ReconcilePerconaServerMongoDBBackup) checkFinalizers(ctx context.Contex
317317
for _, f := range cr.GetFinalizers() {
318318
switch f {
319319
case "delete-backup":
320-
if len(cr.Status.PBMname) == 0 {
321-
continue
322-
}
323-
metaNotFound := false
324-
if b.pbm != nil {
325-
_, err := b.pbm.C.GetBackupMeta(cr.Status.PBMname)
326-
if err != nil {
327-
if !errors.Is(err, pbm.ErrNotFound) {
328-
return errors.Wrap(err, "get backup meta")
329-
}
330-
metaNotFound = true
331-
}
332-
}
333-
if b.pbm == nil || metaNotFound {
334-
dummyPBM := new(pbm.PBM) // We need this only for the DeleteBackupFiles method, which doesn't use method receiver at all
335-
stg, err := r.getPBMStorage(ctx, cr)
336-
if err != nil {
337-
return errors.Wrap(err, "get storage")
338-
}
339-
if err := dummyPBM.DeleteBackupFiles(getPBMBackupMeta(cr), stg); err != nil {
340-
log.Error(err, "failed to run finalizer with dummy pbm", "finalizer", f)
341-
finalizers = append(finalizers, f)
342-
}
343-
continue
344-
}
345-
346-
if cluster == nil {
347-
return errors.Errorf("PerconaServerMongoDB %s is not found", cr.Spec.GetClusterName())
348-
}
349-
350-
var storage psmdbv1.BackupStorageSpec
351-
switch {
352-
case cr.Status.S3 != nil:
353-
storage.Type = psmdbv1.BackupStorageS3
354-
storage.S3 = *cr.Status.S3
355-
case cr.Status.Azure != nil:
356-
storage.Type = psmdbv1.BackupStorageAzure
357-
storage.Azure = *cr.Status.Azure
358-
}
359-
360-
err := b.pbm.SetConfig(ctx, r.client, cluster, storage)
361-
if err != nil {
362-
return errors.Wrapf(err, "set backup config with storage %s", cr.Spec.StorageName)
363-
}
364-
e := b.pbm.C.Logger().NewEvent(string(pbm.CmdDeleteBackup), "", "", primitive.Timestamp{})
365-
err = b.pbm.C.DeleteBackup(cr.Status.PBMname, e)
366-
if err != nil {
320+
if err := r.deleteBackupFinalizer(ctx, cr, cluster, b); err != nil {
367321
log.Error(err, "failed to run finalizer", "finalizer", f)
368322
finalizers = append(finalizers, f)
369323
}
@@ -377,6 +331,108 @@ func (r *ReconcilePerconaServerMongoDBBackup) checkFinalizers(ctx context.Contex
377331
return err
378332
}
379333

334+
func (r *ReconcilePerconaServerMongoDBBackup) deleteBackupFinalizer(ctx context.Context, cr *psmdbv1.PerconaServerMongoDBBackup, cluster *psmdbv1.PerconaServerMongoDB, b *Backup) error {
335+
if len(cr.Status.PBMname) == 0 {
336+
return nil
337+
}
338+
339+
var meta *pbm.BackupMeta
340+
var err error
341+
342+
if b.pbm != nil {
343+
meta, err = b.pbm.C.GetBackupMeta(cr.Status.PBMname)
344+
if err != nil {
345+
if !errors.Is(err, pbm.ErrNotFound) {
346+
return errors.Wrap(err, "get backup meta")
347+
}
348+
meta = nil
349+
}
350+
}
351+
if b.pbm == nil || meta == nil {
352+
dummyPBM := new(pbm.PBM) // We need this only for the DeleteBackupFiles method, which doesn't use method receiver at all
353+
stg, err := r.getPBMStorage(ctx, cr)
354+
if err != nil {
355+
return errors.Wrap(err, "get storage")
356+
}
357+
if err := dummyPBM.DeleteBackupFiles(getPBMBackupMeta(cr), stg); err != nil {
358+
return errors.Wrap(err, "failed to delete backup files with dummy PBM")
359+
}
360+
return nil
361+
}
362+
363+
if cluster == nil {
364+
return errors.Errorf("PerconaServerMongoDB %s is not found", cr.Spec.GetClusterName())
365+
}
366+
367+
var storage psmdbv1.BackupStorageSpec
368+
switch {
369+
case cr.Status.S3 != nil:
370+
storage.Type = psmdbv1.BackupStorageS3
371+
storage.S3 = *cr.Status.S3
372+
case cr.Status.Azure != nil:
373+
storage.Type = psmdbv1.BackupStorageAzure
374+
storage.Azure = *cr.Status.Azure
375+
}
376+
377+
err = b.pbm.SetConfig(ctx, r.client, cluster, storage)
378+
if err != nil {
379+
return errors.Wrapf(err, "set backup config with storage %s", cr.Spec.StorageName)
380+
}
381+
e := b.pbm.C.Logger().NewEvent(string(pbm.CmdDeleteBackup), "", "", primitive.Timestamp{})
382+
// We should delete PITR oplog chunks until `LastWriteTS` of the backup,
383+
// as it's not possible to delete backup if it is a base for the PITR timeline
384+
err = r.deletePITR(ctx, b, meta.LastWriteTS, e)
385+
if err != nil {
386+
return errors.Wrap(err, "failed to delete PITR")
387+
}
388+
err = b.pbm.C.DeleteBackup(cr.Status.PBMname, e)
389+
if err != nil {
390+
return errors.Wrap(err, "failed to delete backup")
391+
}
392+
return nil
393+
}
394+
395+
// deletePITR deletes PITR oplog chunks whose StartTS is less or equal to the `until` timestamp. Deletes all chunks if `until` is 0.
396+
func (r *ReconcilePerconaServerMongoDBBackup) deletePITR(ctx context.Context, b *Backup, until primitive.Timestamp, e *pbmLog.Event) error {
397+
log := logf.FromContext(ctx)
398+
399+
stg, err := b.pbm.C.GetStorage(e)
400+
if err != nil {
401+
return errors.Wrap(err, "get storage")
402+
}
403+
404+
chunks, err := b.pbm.C.PITRGetChunksSlice("", primitive.Timestamp{}, until)
405+
if err != nil {
406+
return errors.Wrap(err, "get pitr chunks")
407+
}
408+
if len(chunks) == 0 {
409+
log.Info("nothing to delete")
410+
}
411+
412+
for _, chnk := range chunks {
413+
err = stg.Delete(chnk.FName)
414+
if err != nil && err != storage.ErrNotExist {
415+
return errors.Wrapf(err, "delete pitr chunk '%s' (%v) from storage", chnk.FName, chnk)
416+
}
417+
418+
_, err = b.pbm.C.Conn.Database(pbm.DB).Collection(pbm.PITRChunksCollection).DeleteOne(
419+
ctx,
420+
bson.D{
421+
{Key: "rs", Value: chnk.RS},
422+
{Key: "start_ts", Value: chnk.StartTS},
423+
{Key: "end_ts", Value: chnk.EndTS},
424+
},
425+
)
426+
427+
if err != nil {
428+
return errors.Wrap(err, "delete pitr chunk metadata")
429+
}
430+
431+
log.Info("deleted " + chnk.FName)
432+
}
433+
return nil
434+
}
435+
380436
func (r *ReconcilePerconaServerMongoDBBackup) updateStatus(ctx context.Context, cr *psmdbv1.PerconaServerMongoDBBackup) error {
381437
err := retry.RetryOnConflict(retry.DefaultRetry, func() error {
382438
c := &psmdbv1.PerconaServerMongoDBBackup{}

0 commit comments

Comments
 (0)