Skip to content
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
27 changes: 27 additions & 0 deletions percona/controller/pgbackup/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -409,6 +409,33 @@ func ensureFinalizers(ctx context.Context, cl client.Client, pgBackup *v2.Percon
func deleteBackupFinalizer(c client.Client, pg *v2.PerconaPGCluster) func(ctx context.Context, pgBackup *v2.PerconaPGBackup) error {
return func(ctx context.Context, pgBackup *v2.PerconaPGBackup) error {
if pg == nil {
// If the cluster was previously deleted, we cannot call finishBackup to remove
// annotations from the PGCluster. In that case, we no longer need the job to
// exist and we can remove the keep-job finalizer.
Comment thread
pooknull marked this conversation as resolved.
Comment on lines +412 to +414

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand why the removal of keep-job is tied to the execution of delete-backup finalizer. Doesn't this mean we will still have this issue if the delete-backup finalizer is not present?

Shouldn't we instead set a watch on the Job and Cluster, requeue events for corresponding PGBackup and at the top of Reconcile remove the finalizer on the Job when pgCluster == nil (or if backup is in a terminal state)?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The delete-backup finalizer is added before the backup job is created. So a backup job with keep-job cannot exist without the corresponding pgbackup having the delete-backup finalizer.

The reason for tying keep-job removal to the delete-backup finalizer is the idea that delete-backup owns the backup cleanup sequence: it removes the backup labels and annotations from the cluster and then releases keep-job in the required order. If the cluster no longer exists, releasing keep-job is still the remaining part of the same cleanup operation, so it belongs in this finalizer.

Moving this logic to the top of Reconcile would not change the behavior unless someone manually removed the delete-backup finalizer, which users should not touch.

job, err := findBackupJob(ctx, c, pgBackup)
if errors.Is(err, ErrBackupJobNotFound) || k8serrors.IsNotFound(err) {
return nil
}
if err != nil {
return errors.Wrap(err, "find backup job")
}

if !controllerutil.ContainsFinalizer(job, pNaming.FinalizerKeepJob) {
return nil
}

if err := retry.RetryOnConflict(retry.DefaultBackoff, func() error {
j := new(batchv1.Job)
if err := c.Get(ctx, client.ObjectKeyFromObject(job), j); err != nil {
return client.IgnoreNotFound(err)
}

controllerutil.RemoveFinalizer(j, pNaming.FinalizerKeepJob)
return c.Update(ctx, j)
Comment thread
pooknull marked this conversation as resolved.
}); err != nil {
return errors.Wrap(err, "remove keep-job finalizer")
}

return nil
}

Expand Down
20 changes: 19 additions & 1 deletion percona/controller/pgbackup/controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
batchv1 "k8s.io/api/batch/v1"
coordinationv1 "k8s.io/api/coordination/v1"
corev1 "k8s.io/api/core/v1"
k8serrors "k8s.io/apimachinery/pkg/api/errors"
Expand All @@ -15,6 +16,7 @@ import (
"k8s.io/client-go/kubernetes/scheme"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/client/fake"
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
"sigs.k8s.io/controller-runtime/pkg/reconcile"

"github.com/percona/percona-postgresql-operator/v2/internal/feature"
Expand Down Expand Up @@ -80,7 +82,10 @@ func TestReconcileFailsBackupWhenClusterIsUnavailable(t *testing.T) {
PGCluster: cluster.Name,
RepoName: new("repo1"),
},
Status: v2.PerconaPGBackupStatus{State: v2.BackupRunning},
Status: v2.PerconaPGBackupStatus{
State: v2.BackupRunning,
JobName: "test-backup-job",
},
}
if tt.snapshot {
backup.Spec.Method = new(v2.BackupMethodVolumeSnapshot)
Expand All @@ -93,6 +98,13 @@ func TestReconcileFailsBackupWhenClusterIsUnavailable(t *testing.T) {
}

objects := []client.Object{backup}
if !tt.snapshot {
objects = append(objects, &batchv1.Job{ObjectMeta: metav1.ObjectMeta{
Name: backup.Status.JobName,
Namespace: backup.Namespace,
Finalizers: []string{pNaming.FinalizerKeepJob},
}})
}
if tt.snapshot {
holder := backupLeaseHolder(backup)
objects = append(objects, &coordinationv1.Lease{
Expand Down Expand Up @@ -134,6 +146,12 @@ func TestReconcileFailsBackupWhenClusterIsUnavailable(t *testing.T) {
assert.True(t, k8serrors.IsNotFound(err))
} else {
assert.NotContains(t, updated.Finalizers, pNaming.FinalizerDeleteBackup)

job := new(batchv1.Job)
require.NoError(t, cl.Get(ctx, client.ObjectKey{
Name: backup.Status.JobName, Namespace: backup.Namespace,
}, job))
assert.False(t, controllerutil.ContainsFinalizer(job, pNaming.FinalizerKeepJob))
}
})
}
Expand Down
Loading