Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Handle delete before adding finalizer #1177

Merged
merged 1 commit into from
Jul 31, 2023
Merged
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
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
Loading