Skip to content

Commit

Permalink
Merge pull request #1177 from fluxcd/delete-before-finalizer
Browse files Browse the repository at this point in the history
Handle delete before adding finalizer
  • Loading branch information
darkowlzz authored Jul 31, 2023
2 parents 66b93aa + ca0f0ff commit 96f6041
Show file tree
Hide file tree
Showing 13 changed files with 281 additions and 41 deletions.
17 changes: 10 additions & 7 deletions internal/controller/bucket_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -215,16 +215,19 @@ func (r *BucketReconciler) Reconcile(ctx context.Context, req ctrl.Request) (res
r.Metrics.RecordDuration(ctx, obj, start)
}()

// Add finalizer first if not exist to avoid the race condition between init and delete
if !controllerutil.ContainsFinalizer(obj, sourcev1.SourceFinalizer) {
controllerutil.AddFinalizer(obj, sourcev1.SourceFinalizer)
recResult = sreconcile.ResultRequeue
// Examine if the object is under deletion.
if !obj.ObjectMeta.DeletionTimestamp.IsZero() {
recResult, retErr = r.reconcileDelete(ctx, obj)
return
}

// Examine if the object is under deletion
if !obj.ObjectMeta.DeletionTimestamp.IsZero() {
recResult, retErr = r.reconcileDelete(ctx, obj)
// Add finalizer first if not exist to avoid the race condition between init
// and delete.
// Note: Finalizers in general can only be added when the deletionTimestamp
// is not set.
if !controllerutil.ContainsFinalizer(obj, sourcev1.SourceFinalizer) {
controllerutil.AddFinalizer(obj, sourcev1.SourceFinalizer)
recResult = sreconcile.ResultRequeue
return
}

Expand Down
37 changes: 37 additions & 0 deletions internal/controller/bucket_controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/tools/record"
kstatus "sigs.k8s.io/cli-utils/pkg/kstatus/status"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
fakeclient "sigs.k8s.io/controller-runtime/pkg/client/fake"

Expand All @@ -54,6 +55,42 @@ import (
// Environment variable to set the GCP Storage host for the GCP client.
const EnvGcpStorageHost = "STORAGE_EMULATOR_HOST"

func TestBucketReconciler_deleteBeforeFinalizer(t *testing.T) {
g := NewWithT(t)

namespaceName := "bucket-" + randStringRunes(5)
namespace := &corev1.Namespace{
ObjectMeta: metav1.ObjectMeta{Name: namespaceName},
}
g.Expect(k8sClient.Create(ctx, namespace)).ToNot(HaveOccurred())
t.Cleanup(func() {
g.Expect(k8sClient.Delete(ctx, namespace)).NotTo(HaveOccurred())
})

bucket := &bucketv1.Bucket{}
bucket.Name = "test-bucket"
bucket.Namespace = namespaceName
bucket.Spec = bucketv1.BucketSpec{
Interval: metav1.Duration{Duration: interval},
BucketName: "foo",
Endpoint: "bar",
}
// Add a test finalizer to prevent the object from getting deleted.
bucket.SetFinalizers([]string{"test-finalizer"})
g.Expect(k8sClient.Create(ctx, bucket)).NotTo(HaveOccurred())
// Add deletion timestamp by deleting the object.
g.Expect(k8sClient.Delete(ctx, bucket)).NotTo(HaveOccurred())

r := &BucketReconciler{
Client: k8sClient,
EventRecorder: record.NewFakeRecorder(32),
Storage: testStorage,
}
// NOTE: Only a real API server responds with an error in this scenario.
_, err := r.Reconcile(ctx, ctrl.Request{NamespacedName: client.ObjectKeyFromObject(bucket)})
g.Expect(err).NotTo(HaveOccurred())
}

func TestBucketReconciler_Reconcile(t *testing.T) {
g := NewWithT(t)

Expand Down
16 changes: 9 additions & 7 deletions internal/controller/gitrepository_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -209,20 +209,22 @@ func (r *GitRepositoryReconciler) Reconcile(ctx context.Context, req ctrl.Reques
r.Metrics.RecordDuration(ctx, obj, start)
}()

// Examine if the object is under deletion.
if !obj.ObjectMeta.DeletionTimestamp.IsZero() {
recResult, retErr = r.reconcileDelete(ctx, obj)
return
}

// Add finalizer first if not exist to avoid the race condition
// between init and delete
// between init and delete.
// Note: Finalizers in general can only be added when the deletionTimestamp
// is not set.
if !controllerutil.ContainsFinalizer(obj, sourcev1.SourceFinalizer) {
controllerutil.AddFinalizer(obj, sourcev1.SourceFinalizer)
recResult = sreconcile.ResultRequeue
return
}

// Examine if the object is under deletion
if !obj.ObjectMeta.DeletionTimestamp.IsZero() {
recResult, retErr = r.reconcileDelete(ctx, obj)
return
}

// Return if the object is suspended.
if obj.Spec.Suspend {
log.Info("reconciliation is suspended for this object")
Expand Down
35 changes: 35 additions & 0 deletions internal/controller/gitrepository_controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,41 @@ Oomb3gD/TRf/nAdVED+k81GdLzciYdUGtI71/qI47G0nMBluLRE=
`
)

func TestGitRepositoryReconciler_deleteBeforeFinalizer(t *testing.T) {
g := NewWithT(t)

namespaceName := "gitrepo-" + randStringRunes(5)
namespace := &corev1.Namespace{
ObjectMeta: metav1.ObjectMeta{Name: namespaceName},
}
g.Expect(k8sClient.Create(ctx, namespace)).ToNot(HaveOccurred())
t.Cleanup(func() {
g.Expect(k8sClient.Delete(ctx, namespace)).NotTo(HaveOccurred())
})

gitRepo := &sourcev1.GitRepository{}
gitRepo.Name = "test-gitrepo"
gitRepo.Namespace = namespaceName
gitRepo.Spec = sourcev1.GitRepositorySpec{
Interval: metav1.Duration{Duration: interval},
URL: "https://example.com",
}
// Add a test finalizer to prevent the object from getting deleted.
gitRepo.SetFinalizers([]string{"test-finalizer"})
g.Expect(k8sClient.Create(ctx, gitRepo)).NotTo(HaveOccurred())
// Add deletion timestamp by deleting the object.
g.Expect(k8sClient.Delete(ctx, gitRepo)).NotTo(HaveOccurred())

r := &GitRepositoryReconciler{
Client: k8sClient,
EventRecorder: record.NewFakeRecorder(32),
Storage: testStorage,
}
// NOTE: Only a real API server responds with an error in this scenario.
_, err := r.Reconcile(ctx, ctrl.Request{NamespacedName: client.ObjectKeyFromObject(gitRepo)})
g.Expect(err).NotTo(HaveOccurred())
}

func TestGitRepositoryReconciler_Reconcile(t *testing.T) {
g := NewWithT(t)

Expand Down
16 changes: 9 additions & 7 deletions internal/controller/helmchart_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -230,20 +230,22 @@ func (r *HelmChartReconciler) Reconcile(ctx context.Context, req ctrl.Request) (
r.Metrics.RecordDuration(ctx, obj, start)
}()

// Examine if the object is under deletion.
if !obj.ObjectMeta.DeletionTimestamp.IsZero() {
recResult, retErr = r.reconcileDelete(ctx, obj)
return
}

// Add finalizer first if not exist to avoid the race condition
// between init and delete
// between init and delete.
// Note: Finalizers in general can only be added when the deletionTimestamp
// is not set.
if !controllerutil.ContainsFinalizer(obj, sourcev1.SourceFinalizer) {
controllerutil.AddFinalizer(obj, sourcev1.SourceFinalizer)
recResult = sreconcile.ResultRequeue
return
}

// Examine if the object is under deletion
if !obj.ObjectMeta.DeletionTimestamp.IsZero() {
recResult, retErr = r.reconcileDelete(ctx, obj)
return
}

// Return if the object is suspended.
if obj.Spec.Suspend {
log.Info("Reconciliation is suspended for this object")
Expand Down
40 changes: 40 additions & 0 deletions internal/controller/helmchart_controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/tools/record"
kstatus "sigs.k8s.io/cli-utils/pkg/kstatus/status"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
fakeclient "sigs.k8s.io/controller-runtime/pkg/client/fake"
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
Expand All @@ -66,6 +67,45 @@ import (
"github.com/fluxcd/source-controller/internal/reconcile/summarize"
)

func TestHelmChartReconciler_deleteBeforeFinalizer(t *testing.T) {
g := NewWithT(t)

namespaceName := "helmchart-" + randStringRunes(5)
namespace := &corev1.Namespace{
ObjectMeta: metav1.ObjectMeta{Name: namespaceName},
}
g.Expect(k8sClient.Create(ctx, namespace)).ToNot(HaveOccurred())
t.Cleanup(func() {
g.Expect(k8sClient.Delete(ctx, namespace)).NotTo(HaveOccurred())
})

helmchart := &helmv1.HelmChart{}
helmchart.Name = "test-helmchart"
helmchart.Namespace = namespaceName
helmchart.Spec = helmv1.HelmChartSpec{
Interval: metav1.Duration{Duration: interval},
Chart: "foo",
SourceRef: helmv1.LocalHelmChartSourceReference{
Kind: "HelmRepository",
Name: "bar",
},
}
// Add a test finalizer to prevent the object from getting deleted.
helmchart.SetFinalizers([]string{"test-finalizer"})
g.Expect(k8sClient.Create(ctx, helmchart)).NotTo(HaveOccurred())
// Add deletion timestamp by deleting the object.
g.Expect(k8sClient.Delete(ctx, helmchart)).NotTo(HaveOccurred())

r := &HelmChartReconciler{
Client: k8sClient,
EventRecorder: record.NewFakeRecorder(32),
Storage: testStorage,
}
// NOTE: Only a real API server responds with an error in this scenario.
_, err := r.Reconcile(ctx, ctrl.Request{NamespacedName: client.ObjectKeyFromObject(helmchart)})
g.Expect(err).NotTo(HaveOccurred())
}

func TestHelmChartReconciler_Reconcile(t *testing.T) {
g := NewWithT(t)

Expand Down
17 changes: 9 additions & 8 deletions internal/controller/helmrepository_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -191,21 +191,22 @@ func (r *HelmRepositoryReconciler) Reconcile(ctx context.Context, req ctrl.Reque
r.Metrics.RecordDuration(ctx, obj, start)
}()

// Examine if the object is under deletion or if a type change has happened.
if !obj.ObjectMeta.DeletionTimestamp.IsZero() || (obj.Spec.Type != "" && obj.Spec.Type != helmv1.HelmRepositoryTypeDefault) {
recResult, retErr = r.reconcileDelete(ctx, obj)
return
}

// Add finalizer first if not exist to avoid the race condition
// between init and delete
// between init and delete.
// Note: Finalizers in general can only be added when the deletionTimestamp
// is not set.
if !controllerutil.ContainsFinalizer(obj, sourcev1.SourceFinalizer) {
controllerutil.AddFinalizer(obj, sourcev1.SourceFinalizer)
recResult = sreconcile.ResultRequeue
return
}

// Examine if the object is under deletion
// or if a type change has happened
if !obj.ObjectMeta.DeletionTimestamp.IsZero() || (obj.Spec.Type != "" && obj.Spec.Type != helmv1.HelmRepositoryTypeDefault) {
recResult, retErr = r.reconcileDelete(ctx, obj)
return
}

// Return if the object is suspended.
if obj.Spec.Suspend {
log.Info("reconciliation is suspended for this object")
Expand Down
12 changes: 7 additions & 5 deletions internal/controller/helmrepository_controller_oci.go
Original file line number Diff line number Diff line change
Expand Up @@ -175,18 +175,20 @@ func (r *HelmRepositoryOCIReconciler) Reconcile(ctx context.Context, req ctrl.Re
r.Metrics.RecordDuration(ctx, obj, start)
}()

// Examine if the object is under deletion.
if !obj.ObjectMeta.DeletionTimestamp.IsZero() {
return r.reconcileDelete(ctx, obj)
}

// Add finalizer first if it doesn't exist to avoid the race condition
// between init and delete.
// Note: Finalizers in general can only be added when the deletionTimestamp
// is not set.
if !controllerutil.ContainsFinalizer(obj, sourcev1.SourceFinalizer) {
controllerutil.AddFinalizer(obj, sourcev1.SourceFinalizer)
return ctrl.Result{Requeue: true}, nil
}

// Examine if the object is under deletion.
if !obj.ObjectMeta.DeletionTimestamp.IsZero() {
return r.reconcileDelete(ctx, obj)
}

// Return if the object is suspended.
if obj.Spec.Suspend {
log.Info("reconciliation is suspended for this object")
Expand Down
35 changes: 35 additions & 0 deletions internal/controller/helmrepository_controller_oci_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,41 @@ import (
"github.com/fluxcd/source-controller/internal/helm/registry"
)

func TestHelmRepositoryOCIReconciler_deleteBeforeFinalizer(t *testing.T) {
g := NewWithT(t)

namespaceName := "helmrepo-" + randStringRunes(5)
namespace := &corev1.Namespace{
ObjectMeta: metav1.ObjectMeta{Name: namespaceName},
}
g.Expect(k8sClient.Create(ctx, namespace)).ToNot(HaveOccurred())
t.Cleanup(func() {
g.Expect(k8sClient.Delete(ctx, namespace)).NotTo(HaveOccurred())
})

helmrepo := &helmv1.HelmRepository{}
helmrepo.Name = "test-helmrepo"
helmrepo.Namespace = namespaceName
helmrepo.Spec = helmv1.HelmRepositorySpec{
Interval: metav1.Duration{Duration: interval},
URL: "https://example.com",
Type: "oci",
}
// Add a test finalizer to prevent the object from getting deleted.
helmrepo.SetFinalizers([]string{"test-finalizer"})
g.Expect(k8sClient.Create(ctx, helmrepo)).NotTo(HaveOccurred())
// Add deletion timestamp by deleting the object.
g.Expect(k8sClient.Delete(ctx, helmrepo)).NotTo(HaveOccurred())

r := &HelmRepositoryOCIReconciler{
Client: k8sClient,
EventRecorder: record.NewFakeRecorder(32),
}
// NOTE: Only a real API server responds with an error in this scenario.
_, err := r.Reconcile(ctx, ctrl.Request{NamespacedName: client.ObjectKeyFromObject(helmrepo)})
g.Expect(err).NotTo(HaveOccurred())
}

func TestHelmRepositoryOCIReconciler_Reconcile(t *testing.T) {
tests := []struct {
name string
Expand Down
36 changes: 36 additions & 0 deletions internal/controller/helmrepository_controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/tools/record"
kstatus "sigs.k8s.io/cli-utils/pkg/kstatus/status"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
fakeclient "sigs.k8s.io/controller-runtime/pkg/client/fake"

Expand All @@ -56,6 +57,41 @@ import (
"github.com/fluxcd/source-controller/internal/reconcile/summarize"
)

func TestHelmRepositoryReconciler_deleteBeforeFinalizer(t *testing.T) {
g := NewWithT(t)

namespaceName := "helmrepo-" + randStringRunes(5)
namespace := &corev1.Namespace{
ObjectMeta: metav1.ObjectMeta{Name: namespaceName},
}
g.Expect(k8sClient.Create(ctx, namespace)).ToNot(HaveOccurred())
t.Cleanup(func() {
g.Expect(k8sClient.Delete(ctx, namespace)).NotTo(HaveOccurred())
})

helmrepo := &helmv1.HelmRepository{}
helmrepo.Name = "test-helmrepo"
helmrepo.Namespace = namespaceName
helmrepo.Spec = helmv1.HelmRepositorySpec{
Interval: metav1.Duration{Duration: interval},
URL: "https://example.com",
}
// Add a test finalizer to prevent the object from getting deleted.
helmrepo.SetFinalizers([]string{"test-finalizer"})
g.Expect(k8sClient.Create(ctx, helmrepo)).NotTo(HaveOccurred())
// Add deletion timestamp by deleting the object.
g.Expect(k8sClient.Delete(ctx, helmrepo)).NotTo(HaveOccurred())

r := &HelmRepositoryReconciler{
Client: k8sClient,
EventRecorder: record.NewFakeRecorder(32),
Storage: testStorage,
}
// NOTE: Only a real API server responds with an error in this scenario.
_, err := r.Reconcile(ctx, ctrl.Request{NamespacedName: client.ObjectKeyFromObject(helmrepo)})
g.Expect(err).NotTo(HaveOccurred())
}

func TestHelmRepositoryReconciler_Reconcile(t *testing.T) {
g := NewWithT(t)

Expand Down
Loading

0 comments on commit 96f6041

Please sign in to comment.