Skip to content

Commit 4f23760

Browse files
committed
Handle finalizers in the fake client
The fake client differs significantly from the real implementation. With the real client implementation, upon the deletion of a resource, when finalizers are set the DeletionTimestamp is filled with a non-null value. This triggers a number of reconciling loops for all controllers that then can take the opportunity to inspect the resource being deleted, perform the required cleaning actions and then update the resource by removing their finalizers as the cleaning job have been done. In the current implementation, the Delete function implements a straight delete and hence triggers a false positive or negative test when trying to run tests at the client level, without interacting with the objects. For example: ``` client.CreateObject() controller.Reconcile() Expect(Something).To(BeDone()) client.DeleteObject() controller.Reconcile() Expect(SomethingElse).To(BeDone()) ``` In the real life, SomethingElse would actually be done as the controller would still have access to the resource. In the current fake implementation, this is not the case as the controller does not have access to the resource any longer.
1 parent e388e1e commit 4f23760

File tree

2 files changed

+95
-0
lines changed

2 files changed

+95
-0
lines changed

pkg/client/fake/client.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -248,6 +248,9 @@ func (t versionedTracker) Update(gvr schema.GroupVersionResource, obj runtime.Ob
248248
}
249249
intResourceVersion++
250250
accessor.SetResourceVersion(strconv.FormatUint(intResourceVersion, 10))
251+
if !accessor.GetDeletionTimestamp().IsZero() && len(accessor.GetFinalizers()) == 0 {
252+
return t.ObjectTracker.Delete(gvr, accessor.GetNamespace(), accessor.GetName())
253+
}
251254
return t.ObjectTracker.Update(gvr, obj, ns)
252255
}
253256

@@ -389,6 +392,18 @@ func (c *fakeClient) Delete(ctx context.Context, obj client.Object, opts ...clie
389392
delOptions := client.DeleteOptions{}
390393
delOptions.ApplyOptions(opts)
391394

395+
old, err := c.tracker.Get(gvr, accessor.GetNamespace(), accessor.GetName())
396+
if err == nil {
397+
oldAccessor, err := meta.Accessor(old)
398+
if err == nil {
399+
if len(oldAccessor.GetFinalizers()) > 0 {
400+
now := metav1.Now()
401+
oldAccessor.SetDeletionTimestamp(&now)
402+
return c.tracker.Update(gvr, old, accessor.GetNamespace())
403+
}
404+
}
405+
}
406+
392407
//TODO: implement propagation
393408
return c.tracker.Delete(gvr, accessor.GetNamespace(), accessor.GetName())
394409
}

pkg/client/fake/client_test.go

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -443,6 +443,46 @@ var _ = Describe("Fake client", func() {
443443
Expect(list.Items).To(ConsistOf(*dep2))
444444
})
445445

446+
It("should handle finalizers on Update", func() {
447+
namespacedName := types.NamespacedName{
448+
Name: "test-cm",
449+
Namespace: "delete-with-finalizers",
450+
}
451+
By("Updating a new object")
452+
newObj := &corev1.ConfigMap{
453+
ObjectMeta: metav1.ObjectMeta{
454+
Name: namespacedName.Name,
455+
Namespace: namespacedName.Namespace,
456+
Finalizers: []string{"finalizers.sigs.k8s.io/test"},
457+
},
458+
Data: map[string]string{
459+
"test-key": "new-value",
460+
},
461+
}
462+
err := cl.Create(context.Background(), newObj)
463+
Expect(err).To(BeNil())
464+
465+
By("Deleting the object")
466+
err = cl.Delete(context.Background(), newObj)
467+
Expect(err).To(BeNil())
468+
469+
By("Getting the object")
470+
obj := &corev1.ConfigMap{}
471+
err = cl.Get(context.Background(), namespacedName, obj)
472+
Expect(err).To(BeNil())
473+
Expect(obj.DeletionTimestamp).NotTo(BeNil())
474+
475+
By("Removing the finalizer")
476+
obj.Finalizers = []string{}
477+
err = cl.Update(context.Background(), obj)
478+
Expect(err).To(BeNil())
479+
480+
By("Getting the object")
481+
obj = &corev1.ConfigMap{}
482+
err = cl.Get(context.Background(), namespacedName, obj)
483+
Expect(apierrors.IsNotFound(err)).To(BeTrue())
484+
})
485+
446486
It("should be able to Delete a Collection", func() {
447487
By("Deleting a deploymentList")
448488
err := cl.DeleteAllOf(context.Background(), &appsv1.Deployment{}, client.InNamespace("ns1"))
@@ -531,6 +571,46 @@ var _ = Describe("Fake client", func() {
531571
Expect(obj.Annotations["foo"]).To(Equal("bar"))
532572
Expect(obj.ObjectMeta.ResourceVersion).To(Equal("1000"))
533573
})
574+
575+
It("should handle finalizers on Patch", func() {
576+
namespacedName := types.NamespacedName{
577+
Name: "test-cm",
578+
Namespace: "delete-with-finalizers",
579+
}
580+
By("Updating a new object")
581+
now := metav1.Now()
582+
newObj := &corev1.ConfigMap{
583+
ObjectMeta: metav1.ObjectMeta{
584+
Name: namespacedName.Name,
585+
Namespace: namespacedName.Namespace,
586+
Finalizers: []string{"finalizers.sigs.k8s.io/test"},
587+
DeletionTimestamp: &now,
588+
},
589+
Data: map[string]string{
590+
"test-key": "new-value",
591+
},
592+
}
593+
err := cl.Create(context.Background(), newObj)
594+
Expect(err).To(BeNil())
595+
596+
By("Removing the finalizer")
597+
obj := &corev1.ConfigMap{
598+
ObjectMeta: metav1.ObjectMeta{
599+
Name: namespacedName.Name,
600+
Namespace: namespacedName.Namespace,
601+
Finalizers: []string{},
602+
DeletionTimestamp: &now,
603+
},
604+
}
605+
obj.Finalizers = []string{}
606+
err = cl.Patch(context.Background(), obj, client.MergeFrom(newObj))
607+
Expect(err).To(BeNil())
608+
609+
By("Getting the object")
610+
obj = &corev1.ConfigMap{}
611+
err = cl.Get(context.Background(), namespacedName, obj)
612+
Expect(apierrors.IsNotFound(err)).To(BeTrue())
613+
})
534614
}
535615

536616
Context("with default scheme.Scheme", func() {

0 commit comments

Comments
 (0)