Skip to content

Commit 1f832fc

Browse files
committed
Fix reconciliation logic for existing and deleted objects
This PR addresses two cases: - Controller Restarts: When the controller restarts, it does not handle deletions for removed objects. Specifically, when an object with a deletion timestamp is passed into the Add() method, the deletion process was not handled correctly. - Recreation of Objects: The recreation of objects is not functioning as expected. For example, when an object is created, deleted, and then created again. In this scenario, the object may enter the Update method, where the creation process was not being handled properly. Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
1 parent c2f6e65 commit 1f832fc

File tree

6 files changed

+248
-52
lines changed

6 files changed

+248
-52
lines changed

controller/pkg/bucketclaim/bucketclaim.go

Lines changed: 30 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,10 @@ func NewBucketClaimListener() *BucketClaimListener {
3232

3333
// Add creates a bucket in response to a bucketClaim
3434
func (b *BucketClaimListener) Add(ctx context.Context, bucketClaim *v1alpha1.BucketClaim) error {
35+
if !bucketClaim.GetDeletionTimestamp().IsZero() {
36+
return b.handleDeletion(ctx, bucketClaim)
37+
}
38+
3539
klog.V(3).InfoS("Add BucketClaim",
3640
"name", bucketClaim.ObjectMeta.Name,
3741
"ns", bucketClaim.ObjectMeta.Namespace,
@@ -76,18 +80,11 @@ func (b *BucketClaimListener) Update(ctx context.Context, old, new *v1alpha1.Buc
7680
bucketClaim := new.DeepCopy()
7781

7882
if !new.GetDeletionTimestamp().IsZero() {
79-
if controllerutil.ContainsFinalizer(bucketClaim, util.BucketClaimFinalizer) {
80-
bucketName := bucketClaim.Status.BucketName
81-
err := b.buckets().Delete(ctx, bucketName, metav1.DeleteOptions{})
82-
if err != nil {
83-
klog.V(3).ErrorS(err, "Error deleting bucket",
84-
"bucket", bucketName,
85-
"bucketClaim", bucketClaim.ObjectMeta.Name)
86-
return b.recordError(bucketClaim, v1.EventTypeWarning, v1alpha1.FailedDeleteBucket, err)
87-
}
88-
89-
klog.V(5).Infof("Successfully deleted bucket: %s from bucketClaim: %s", bucketName, bucketClaim.ObjectMeta.Name)
90-
}
83+
return b.handleDeletion(ctx, bucketClaim)
84+
}
85+
86+
if err := b.Add(ctx, bucketClaim); err != nil {
87+
return err
9188
}
9289

9390
klog.V(3).InfoS("Update BucketClaim success",
@@ -96,6 +93,27 @@ func (b *BucketClaimListener) Update(ctx context.Context, old, new *v1alpha1.Buc
9693
return nil
9794
}
9895

96+
// handleDeletion processes the deletion of a bucketClaim.
97+
func (b *BucketClaimListener) handleDeletion(ctx context.Context, bucketClaim *v1alpha1.BucketClaim) error {
98+
if !controllerutil.ContainsFinalizer(bucketClaim, util.BucketClaimFinalizer) {
99+
return nil
100+
}
101+
102+
bucketName := bucketClaim.Status.BucketName
103+
if bucketName != "" {
104+
if err := b.buckets().Delete(ctx, bucketName, metav1.DeleteOptions{}); err != nil && !kubeerrors.IsNotFound(err) {
105+
klog.V(3).ErrorS(err, "Error deleting bucket",
106+
"bucket", bucketName,
107+
"bucketClaim", bucketClaim.ObjectMeta.Name)
108+
return b.recordError(bucketClaim, v1.EventTypeWarning, v1alpha1.FailedDeleteBucket, err)
109+
}
110+
klog.V(5).Infof("Successfully requested deletion of bucket: %s for bucketClaim: %s",
111+
bucketName, bucketClaim.ObjectMeta.Name)
112+
}
113+
114+
return nil
115+
}
116+
99117
// Delete processes a bucket for which bucket request is deleted
100118
func (b *BucketClaimListener) Delete(ctx context.Context, bucketClaim *v1alpha1.BucketClaim) error {
101119
klog.V(3).InfoS("Delete BucketClaim",

controller/pkg/bucketclaim/bucketclaim_test.go

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -323,3 +323,32 @@ func TestRecordEvents(t *testing.T) {
323323
func newEvent(eventType, reason, message string) string {
324324
return fmt.Sprintf("%s %s %s", eventType, reason, message)
325325
}
326+
327+
// Claim already marked for deletion must not create a bucket
328+
func TestAddDeletedBucketClaim(t *testing.T) {
329+
ctx, cancel := context.WithCancel(context.Background())
330+
defer cancel()
331+
332+
client := fakebucketclientset.NewSimpleClientset()
333+
kubeClient := fakekubeclientset.NewSimpleClientset()
334+
eventRecorder := record.NewFakeRecorder(3)
335+
336+
listener := NewBucketClaimListener()
337+
listener.InitializeKubeClient(kubeClient)
338+
listener.InitializeBucketClient(client)
339+
listener.InitializeEventRecorder(eventRecorder)
340+
341+
_, _ = util.CreateBucketClass(ctx, client, &goldClass)
342+
343+
claimToDelete := bucketClaim1.DeepCopy()
344+
now := metav1.Now()
345+
claimToDelete.ObjectMeta.DeletionTimestamp = &now
346+
347+
if err := listener.Add(ctx, claimToDelete); err != nil {
348+
t.Fatalf("Add returned error for deleted claim: %v", err)
349+
}
350+
351+
if bl := util.GetBuckets(ctx, client, 0); len(bl.Items) != 0 {
352+
t.Fatalf("expected 0 buckets, got %d", len(bl.Items))
353+
}
354+
}

sidecar/pkg/bucket/bucket_controller.go

Lines changed: 47 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,10 @@ func (b *BucketListener) Add(ctx context.Context, inputBucket *v1alpha1.Bucket)
6868

6969
var err error
7070

71+
if !bucket.GetDeletionTimestamp().IsZero() {
72+
return b.handleDeletion(ctx, bucket)
73+
}
74+
7175
klog.V(3).InfoS("Add Bucket",
7276
"name", bucket.ObjectMeta.Name)
7377

@@ -212,55 +216,60 @@ func (b *BucketListener) Update(ctx context.Context, old, new *v1alpha1.Bucket)
212216
var err error
213217

214218
if !bucket.GetDeletionTimestamp().IsZero() {
215-
if controllerutil.ContainsFinalizer(bucket, consts.BABucketFinalizer) {
216-
bucketClaimNs := bucket.Spec.BucketClaim.Namespace
217-
bucketClaimName := bucket.Spec.BucketClaim.Name
218-
bucketAccessList, err := b.bucketAccesses(bucketClaimNs).List(ctx, metav1.ListOptions{})
219-
if err != nil {
220-
klog.V(3).ErrorS(err, "Error fetching BucketAccessList",
221-
"bucket", bucket.ObjectMeta.Name)
222-
return err
223-
}
224-
225-
for _, bucketAccess := range bucketAccessList.Items {
226-
if strings.EqualFold(bucketAccess.Spec.BucketClaimName, bucketClaimName) {
227-
err = b.bucketAccesses(bucketClaimNs).Delete(ctx, bucketAccess.Name, metav1.DeleteOptions{})
228-
if err != nil {
229-
klog.V(3).ErrorS(err, "Error deleting BucketAccess",
230-
"name", bucketAccess.Name,
231-
"bucket", bucket.ObjectMeta.Name)
232-
return err
233-
}
234-
}
235-
}
219+
return b.handleDeletion(ctx, bucket)
220+
}
236221

237-
klog.V(5).Infof("Successfully deleted dependent bucketAccess of bucket:%s", bucket.ObjectMeta.Name)
222+
if err = b.Add(ctx, bucket); err != nil {
223+
return err
224+
}
238225

239-
controllerutil.RemoveFinalizer(bucket, consts.BABucketFinalizer)
240-
klog.V(5).Infof("Successfully removed finalizer: %s of bucket: %s", consts.BABucketFinalizer, bucket.ObjectMeta.Name)
241-
}
226+
klog.V(3).InfoS("Update Bucket success",
227+
"name", bucket.ObjectMeta.Name,
228+
"ns", bucket.ObjectMeta.Namespace)
229+
return nil
230+
}
242231

243-
if controllerutil.ContainsFinalizer(bucket, consts.BucketFinalizer) {
244-
err = b.deleteBucketOp(ctx, bucket)
245-
if err != nil {
246-
return b.recordError(bucket, v1.EventTypeWarning, v1alpha1.FailedDeleteBucket, err)
247-
}
232+
func (b *BucketListener) handleDeletion(ctx context.Context, bucket *v1alpha1.Bucket) error {
233+
var err error
248234

249-
controllerutil.RemoveFinalizer(bucket, consts.BucketFinalizer)
250-
klog.V(5).Infof("Successfully removed finalizer: %s of bucket: %s", consts.BucketFinalizer, bucket.ObjectMeta.Name)
251-
}
235+
if controllerutil.ContainsFinalizer(bucket, consts.BABucketFinalizer) {
236+
bucketClaimNs := bucket.Spec.BucketClaim.Namespace
237+
bucketClaimName := bucket.Spec.BucketClaim.Name
252238

253-
_, err = b.buckets().Update(ctx, bucket, metav1.UpdateOptions{})
239+
bucketAccessList, err := b.bucketAccesses(bucketClaimNs).List(ctx, metav1.ListOptions{})
254240
if err != nil {
255-
klog.V(3).ErrorS(err, "Error updating bucket after removing finalizers",
241+
klog.V(3).ErrorS(err, "Error fetching BucketAccessList",
256242
"bucket", bucket.ObjectMeta.Name)
257243
return err
258244
}
245+
246+
for _, ba := range bucketAccessList.Items {
247+
if strings.EqualFold(ba.Spec.BucketClaimName, bucketClaimName) {
248+
if err = b.bucketAccesses(bucketClaimNs).Delete(ctx, ba.Name, metav1.DeleteOptions{}); err != nil {
249+
klog.V(3).ErrorS(err, "Error deleting BucketAccess",
250+
"name", ba.Name,
251+
"bucket", bucket.ObjectMeta.Name)
252+
return err
253+
}
254+
}
255+
}
256+
257+
controllerutil.RemoveFinalizer(bucket, consts.BABucketFinalizer)
258+
}
259+
260+
if controllerutil.ContainsFinalizer(bucket, consts.BucketFinalizer) {
261+
if err = b.deleteBucketOp(ctx, bucket); err != nil {
262+
return b.recordError(bucket, v1.EventTypeWarning, v1alpha1.FailedDeleteBucket, err)
263+
}
264+
controllerutil.RemoveFinalizer(bucket, consts.BucketFinalizer)
265+
}
266+
267+
if _, err = b.buckets().Update(ctx, bucket, metav1.UpdateOptions{}); err != nil {
268+
klog.V(3).ErrorS(err, "Error updating bucket after removing finalizers",
269+
"bucket", bucket.ObjectMeta.Name)
270+
return err
259271
}
260272

261-
klog.V(3).InfoS("Update Bucket success",
262-
"name", bucket.ObjectMeta.Name,
263-
"ns", bucket.ObjectMeta.Namespace)
264273
return nil
265274
}
266275

sidecar/pkg/bucket/bucket_controller_test.go

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -310,3 +310,43 @@ func TestRecordEvents(t *testing.T) {
310310
func newEvent(eventType, reason, message string) string {
311311
return fmt.Sprintf("%s %s %s", eventType, reason, message)
312312
}
313+
314+
// TestAddDeletedBucket tests that the Add method does not call the driver
315+
func TestAddDeletedBucket(t *testing.T) {
316+
driver := "driver1"
317+
318+
mpc := struct{ fakespec.FakeProvisionerClient }{}
319+
mpc.FakeDriverDeleteBucket = func(
320+
_ context.Context,
321+
_ *cosi.DriverDeleteBucketRequest,
322+
_ ...grpc.CallOption,
323+
) (*cosi.DriverDeleteBucketResponse, error) {
324+
t.Fatalf("driver should NOT be called from Add when object has DeletionTimestamp")
325+
return nil, nil
326+
}
327+
328+
now := metav1.Now()
329+
b := v1alpha1.Bucket{
330+
ObjectMeta: metav1.ObjectMeta{
331+
Name: "testbucket",
332+
DeletionTimestamp: &now,
333+
ResourceVersion: "1",
334+
},
335+
Spec: v1alpha1.BucketSpec{
336+
DriverName: driver,
337+
BucketClassName: "ignored",
338+
},
339+
}
340+
341+
client := fakebucketclientset.NewSimpleClientset(&b)
342+
343+
bl := BucketListener{
344+
driverName: driver,
345+
provisionerClient: &mpc,
346+
}
347+
bl.InitializeBucketClient(client)
348+
349+
if err := bl.Add(context.TODO(), &b); err != nil {
350+
t.Fatalf("Add returned error for deleted bucket: %v", err)
351+
}
352+
}

sidecar/pkg/bucketaccess/bucketaccess_controller.go

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,12 @@ func NewBucketAccessListener(driverName string, client cosi.ProvisionerClient) *
6868
func (bal *BucketAccessListener) Add(ctx context.Context, inputBucketAccess *v1alpha1.BucketAccess) error {
6969
bucketAccess := inputBucketAccess.DeepCopy()
7070

71+
if !bucketAccess.GetDeletionTimestamp().IsZero() {
72+
klog.V(3).InfoS("BucketAccess has deletion timestamp, handling deletion",
73+
"name", bucketAccess.ObjectMeta.Name)
74+
return bal.deleteBucketAccessOp(ctx, bucketAccess)
75+
}
76+
7177
if bucketAccess.Status.AccessGranted && bucketAccess.Status.AccountID != "" {
7278
klog.V(3).InfoS("BucketAccess already exists", bucketAccess.ObjectMeta.Name)
7379
return nil
@@ -310,10 +316,13 @@ func (bal *BucketAccessListener) Update(ctx context.Context, old, new *v1alpha1.
310316

311317
bucketAccess := new.DeepCopy()
312318
if !bucketAccess.GetDeletionTimestamp().IsZero() {
313-
err := bal.deleteBucketAccessOp(ctx, bucketAccess)
314-
if err != nil {
319+
if err := bal.deleteBucketAccessOp(ctx, bucketAccess); err != nil {
315320
return bal.recordError(bucketAccess, v1.EventTypeWarning, v1alpha1.FailedRevokeAccess, err)
316321
}
322+
} else {
323+
if err := bal.Add(ctx, bucketAccess); err != nil {
324+
return bal.recordError(bucketAccess, v1.EventTypeWarning, v1alpha1.FailedGrantAccess, err)
325+
}
317326
}
318327

319328
klog.V(3).InfoS("Update BucketAccess success",

sidecar/pkg/bucketaccess/bucketaccess_controller_test.go

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -502,3 +502,94 @@ func TestRecordEvents(t *testing.T) {
502502
func newEvent(eventType, reason, message string) string {
503503
return fmt.Sprintf("%s %s %s", eventType, reason, message)
504504
}
505+
506+
// TestAddDeletedBucketAccess tests that a deleted BucketAccess does not
507+
// trigger a call to the driver to grant access, and that no secrets are created.
508+
func TestAddDeletedBucketAccess(t *testing.T) {
509+
driver := "driver"
510+
baName := "bucketaccess-deleted"
511+
ns := "testns"
512+
513+
mpc := struct{ fakespec.FakeProvisionerClient }{}
514+
mpc.FakeDriverGrantBucketAccess = func(
515+
_ context.Context,
516+
_ *cosi.DriverGrantBucketAccessRequest,
517+
_ ...grpc.CallOption,
518+
) (*cosi.DriverGrantBucketAccessResponse, error) {
519+
t.Fatalf("driver Grant should NOT be called on deleted BA")
520+
return nil, nil
521+
}
522+
mpc.FakeDriverRevokeBucketAccess = func(
523+
_ context.Context,
524+
_ *cosi.DriverRevokeBucketAccessRequest,
525+
_ ...grpc.CallOption,
526+
) (*cosi.DriverRevokeBucketAccessResponse, error) {
527+
return &cosi.DriverRevokeBucketAccessResponse{}, nil
528+
}
529+
530+
// minimal stub objects just to satisfy look-ups inside delete-path
531+
bac := &v1alpha1.BucketAccessClass{
532+
ObjectMeta: metav1.ObjectMeta{Name: "bac"},
533+
DriverName: driver,
534+
}
535+
claim := &v1alpha1.BucketClaim{
536+
ObjectMeta: metav1.ObjectMeta{Name: "claim", Namespace: ns},
537+
Status: v1alpha1.BucketClaimStatus{
538+
BucketReady: true,
539+
BucketName: "bucket",
540+
},
541+
}
542+
bucket := &v1alpha1.Bucket{
543+
ObjectMeta: metav1.ObjectMeta{Name: "bucket"},
544+
Status: v1alpha1.BucketStatus{
545+
BucketReady: true,
546+
BucketID: "id",
547+
},
548+
}
549+
550+
now := metav1.Now()
551+
ba := &v1alpha1.BucketAccess{
552+
ObjectMeta: metav1.ObjectMeta{
553+
Name: baName,
554+
Namespace: ns,
555+
DeletionTimestamp: &now,
556+
Finalizers: []string{consts.BAFinalizer},
557+
},
558+
Spec: v1alpha1.BucketAccessSpec{
559+
BucketClaimName: claim.Name,
560+
BucketAccessClassName: bac.Name,
561+
CredentialsSecretName: "creds",
562+
},
563+
Status: v1alpha1.BucketAccessStatus{
564+
AccountID: "acc",
565+
AccessGranted: true,
566+
},
567+
}
568+
569+
secret := &v1.Secret{
570+
ObjectMeta: metav1.ObjectMeta{
571+
Name: "creds",
572+
Namespace: ns,
573+
Finalizers: []string{consts.SecretFinalizer},
574+
},
575+
StringData: map[string]string{"dummy": "val"},
576+
}
577+
578+
client := fakebucketclientset.NewSimpleClientset(bac, claim, bucket, ba)
579+
kubeClient := fakekubeclientset.NewSimpleClientset(secret)
580+
581+
bal := BucketAccessListener{
582+
driverName: driver,
583+
provisionerClient: &mpc,
584+
bucketClient: client,
585+
kubeClient: kubeClient,
586+
}
587+
588+
if err := bal.Add(context.TODO(), ba); err != nil {
589+
t.Fatalf("Add returned error for deleted BucketAccess: %v", err)
590+
}
591+
592+
if _, err := bal.secrets(ns).Get(context.TODO(), "creds", metav1.GetOptions{}); !kubeerrors.IsNotFound(err) {
593+
t.Fatalf("secret was not cleaned up, err=%v", err)
594+
}
595+
}

0 commit comments

Comments
 (0)