Skip to content

Commit 1ba8d62

Browse files
authored
controller: handle tombstone objects in delete handlers (antrea-io#7964)
Signed-off-by: OmAmbole009 <omambole09@gmail.com>
1 parent 3070fa1 commit 1ba8d62

10 files changed

Lines changed: 173 additions & 5 deletions

File tree

pkg/controller/egress/controller.go

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -459,7 +459,19 @@ func (c *EgressController) updateEgress(old, cur interface{}) {
459459

460460
// deleteEgress processes Egress DELETE events and deletes corresponding EgressGroup.
461461
func (c *EgressController) deleteEgress(obj interface{}) {
462-
egress := obj.(*egressv1beta1.Egress)
462+
egress, ok := obj.(*egressv1beta1.Egress)
463+
if !ok {
464+
tombstone, ok := obj.(cache.DeletedFinalStateUnknown)
465+
if !ok {
466+
klog.V(2).InfoS("Error decoding object when deleting Egress, invalid type", "object", obj)
467+
return
468+
}
469+
egress, ok = tombstone.Obj.(*egressv1beta1.Egress)
470+
if !ok {
471+
klog.V(2).InfoS("Error decoding object tombstone when deleting Egress, invalid type", "object", tombstone.Obj)
472+
return
473+
}
474+
}
463475
klog.InfoS("Processing Egress DELETE event", "egress", egress.Name)
464476
c.egressGroupStore.Delete(egress.Name)
465477
// Unregister the group from the grouping interface.

pkg/controller/egress/controller_test.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -934,3 +934,22 @@ func TestUpdateEgressAllocatedCondition(t *testing.T) {
934934
})
935935
}
936936
}
937+
938+
func TestDeleteEgress_Tombstone(t *testing.T) {
939+
egress := newEgress("egressA", "", "", &metav1.LabelSelector{}, nil, nil)
940+
egress.UID = "uidA"
941+
controller := newController(nil, []runtime.Object{egress})
942+
943+
// Simulate the controller having processed the Egress ADD event.
944+
controller.addEgress(egress)
945+
_, found, _ := controller.egressGroupStore.Get(egress.Name)
946+
require.True(t, found)
947+
948+
// Deliver the delete as a cache.DeletedFinalStateUnknown tombstone, as the
949+
// informer does after a watch reconnect. This exercises the tombstone path
950+
// in deleteEgress rather than the direct *Egress type assertion.
951+
controller.deleteEgress(cache.DeletedFinalStateUnknown{
952+
Key: egress.Name,
953+
Obj: egress,
954+
})
955+
}

pkg/controller/externalippool/controller.go

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -416,7 +416,19 @@ func (c *ExternalIPPoolController) updateExternalIPPool(_, cur interface{}) {
416416
// deleteExternalIPPool processes ExternalIPPool DELETE events. It deletes the IPAllocator for the pool and triggers
417417
// reconciliation of all consumers that refer to the pool.
418418
func (c *ExternalIPPoolController) deleteExternalIPPool(obj interface{}) {
419-
pool := obj.(*antreacrds.ExternalIPPool)
419+
pool, ok := obj.(*antreacrds.ExternalIPPool)
420+
if !ok {
421+
tombstone, ok := obj.(cache.DeletedFinalStateUnknown)
422+
if !ok {
423+
klog.V(2).InfoS("Error decoding object when deleting ExternalIPPool, invalid type", "object", obj)
424+
return
425+
}
426+
pool, ok = tombstone.Obj.(*antreacrds.ExternalIPPool)
427+
if !ok {
428+
klog.V(2).InfoS("Error decoding object tombstone when deleting ExternalIPPool, invalid type", "object", tombstone.Obj)
429+
return
430+
}
431+
}
420432
klog.InfoS("Processing ExternalIPPool DELETE event", "pool", pool.Name, "ipRanges", pool.Spec.IPRanges)
421433
c.deleteIPAllocator(pool.Name)
422434
// Call consumers to reclaim the IPs allocated from the pool.

pkg/controller/externalippool/controller_test.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -632,3 +632,19 @@ func TestExternalIPPoolController_RestoreIPAllocations(t *testing.T) {
632632
})
633633
}
634634
}
635+
636+
func TestDeleteExternalIPPool_Tombstone(t *testing.T) {
637+
pool := newExternalIPPool("pool1", "10.10.10.0/24", "", "")
638+
c := newController([]runtime.Object{pool})
639+
640+
// Simulate the controller having created an allocator for the pool.
641+
c.createOrUpdateIPAllocator(pool)
642+
require.True(t, c.IPPoolExists(pool.Name))
643+
644+
// Deliver the delete as a tombstone, as the informer does after a watch reconnect.
645+
c.deleteExternalIPPool(cache.DeletedFinalStateUnknown{
646+
Key: pool.Name,
647+
Obj: pool,
648+
})
649+
assert.False(t, c.IPPoolExists(pool.Name))
650+
}

pkg/controller/externalnode/controller.go

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,19 @@ func (c *ExternalNodeController) enqueueExternalNodeUpdate(oldObj interface{}, n
119119
}
120120

121121
func (c *ExternalNodeController) enqueueExternalNodeDelete(obj interface{}) {
122-
en := obj.(*v1alpha1.ExternalNode)
122+
en, ok := obj.(*v1alpha1.ExternalNode)
123+
if !ok {
124+
tombstone, ok := obj.(cache.DeletedFinalStateUnknown)
125+
if !ok {
126+
klog.V(2).InfoS("Error decoding object when deleting ExternalNode, invalid type", "object", obj)
127+
return
128+
}
129+
en, ok = tombstone.Obj.(*v1alpha1.ExternalNode)
130+
if !ok {
131+
klog.V(2).InfoS("Error decoding object tombstone when deleting ExternalNode, invalid type", "object", tombstone.Obj)
132+
return
133+
}
134+
}
123135
key, _ := keyFunc(en)
124136
c.queue.Add(key)
125137
klog.InfoS("Enqueued ExternalNode DELETE event", "ExternalNode", klog.KObj(en))

pkg/controller/externalnode/controller_test.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import (
2828
"k8s.io/apimachinery/pkg/runtime"
2929
"k8s.io/apimachinery/pkg/util/wait"
3030
k8stesting "k8s.io/client-go/testing"
31+
"k8s.io/client-go/tools/cache"
3132

3233
"antrea.io/antrea/v2/pkg/apis/crd/v1alpha1"
3334
"antrea.io/antrea/v2/pkg/apis/crd/v1alpha2"
@@ -663,3 +664,24 @@ func newExternalNodeController(objects []runtime.Object) *ExternalNodeController
663664
externalEntityInformer := informerFactory.Crd().V1alpha2().ExternalEntities()
664665
return NewExternalNodeController(crdClient, externalNodeInformer, externalEntityInformer)
665666
}
667+
668+
func TestEnqueueExternalNodeDelete_Tombstone(t *testing.T) {
669+
externalNode := &v1alpha1.ExternalNode{
670+
ObjectMeta: metav1.ObjectMeta{Name: "vm1", Namespace: "ns1"},
671+
Spec: v1alpha1.ExternalNodeSpec{
672+
Interfaces: []v1alpha1.NetworkInterface{{IPs: []string{"1.1.1.2"}}},
673+
},
674+
}
675+
controller := newExternalNodeController(nil)
676+
expectedKey, _ := keyFunc(externalNode)
677+
678+
// Deliver the delete as a tombstone, as the informer does after a watch reconnect.
679+
controller.enqueueExternalNodeDelete(cache.DeletedFinalStateUnknown{
680+
Key: expectedKey,
681+
Obj: externalNode,
682+
})
683+
684+
require.Equal(t, 1, controller.queue.Len())
685+
key, _ := controller.queue.Get()
686+
assert.Equal(t, expectedKey, key)
687+
}

pkg/controller/labelidentity/controller.go

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,19 @@ func (c *Controller) addLabelIdentity(obj interface{}) {
116116
}
117117

118118
func (c *Controller) deleteLabelIdentity(obj interface{}) {
119-
labelIdentity := obj.(*mcv1alpha1.LabelIdentity)
119+
labelIdentity, ok := obj.(*mcv1alpha1.LabelIdentity)
120+
if !ok {
121+
tombstone, ok := obj.(cache.DeletedFinalStateUnknown)
122+
if !ok {
123+
klog.V(2).InfoS("Error decoding object when deleting LabelIdentity, invalid type", "object", obj)
124+
return
125+
}
126+
labelIdentity, ok = tombstone.Obj.(*mcv1alpha1.LabelIdentity)
127+
if !ok {
128+
klog.V(2).InfoS("Error decoding object tombstone when deleting LabelIdentity, invalid type", "object", tombstone.Obj)
129+
return
130+
}
131+
}
120132
klog.InfoS("Processing LabelIdentity DELETE event", "label", labelIdentity.Spec.Label)
121133
c.labelIdentityIndex.DeleteLabelIdentity(labelIdentity.Spec.Label)
122134
}

pkg/controller/labelidentity/controller_test.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import (
2121
"github.com/stretchr/testify/assert"
2222
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2323
"k8s.io/apimachinery/pkg/runtime"
24+
"k8s.io/client-go/tools/cache"
2425

2526
mcv1alpha1 "antrea.io/antrea/v2/multicluster/apis/multicluster/v1alpha1"
2627
fakeversioned "antrea.io/antrea/v2/multicluster/pkg/client/clientset/versioned/fake"
@@ -81,3 +82,21 @@ func TestGroupEntityControllerRun(t *testing.T) {
8182
return index.HasSynced()
8283
}, 1*time.Second, 10*time.Millisecond, "LabelIdentityIndex hasn't been synced in 1 second after starting LabelIdentityController")
8384
}
85+
86+
func TestDeleteLabelIdentity_Tombstone(t *testing.T) {
87+
index := NewLabelIdentityIndex()
88+
mcClient := fakeversioned.NewSimpleClientset()
89+
mcInformerFactory := crdinformers.NewSharedInformerFactory(mcClient, informerDefaultResync)
90+
c := NewLabelIdentityController(index, mcInformerFactory.Multicluster().V1alpha1().LabelIdentities())
91+
92+
// Add the label identity to the index first.
93+
c.addLabelIdentity(labelIdentityA)
94+
assert.Contains(t, index.labelIdentities, labelIdentityA.Spec.Label)
95+
96+
// Deliver the delete as a tombstone, as the informer does after a watch reconnect.
97+
c.deleteLabelIdentity(cache.DeletedFinalStateUnknown{
98+
Key: labelIdentityA.Name,
99+
Obj: labelIdentityA,
100+
})
101+
assert.NotContains(t, index.labelIdentities, labelIdentityA.Spec.Label)
102+
}

pkg/controller/traceflow/controller.go

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,19 @@ func (c *Controller) updateTraceflow(_, curObj interface{}) {
162162
}
163163

164164
func (c *Controller) deleteTraceflow(old interface{}) {
165-
tf := old.(*crdv1beta1.Traceflow)
165+
tf, ok := old.(*crdv1beta1.Traceflow)
166+
if !ok {
167+
tombstone, ok := old.(cache.DeletedFinalStateUnknown)
168+
if !ok {
169+
klog.V(2).InfoS("Unexpected object type when deleting Traceflow", "object", old)
170+
return
171+
}
172+
tf, ok = tombstone.Obj.(*crdv1beta1.Traceflow)
173+
if !ok {
174+
klog.V(2).InfoS("Unexpected object type in tombstone when deleting Traceflow", "object", tombstone.Obj)
175+
return
176+
}
177+
}
166178
klog.Infof("Processing Traceflow %s DELETE event", tf.Name)
167179
c.deallocateTagForTF(tf)
168180
}

pkg/controller/traceflow/controller_test.go

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import (
2929
clientset "k8s.io/client-go/kubernetes"
3030
"k8s.io/client-go/kubernetes/fake"
3131
k8stesting "k8s.io/client-go/testing"
32+
"k8s.io/client-go/tools/cache"
3233

3334
crdv1beta1 "antrea.io/antrea/v2/pkg/apis/crd/v1beta1"
3435
"antrea.io/antrea/v2/pkg/client/clientset/versioned"
@@ -197,3 +198,34 @@ func newCRDClientset() *fakeversioned.Clientset {
197198

198199
return client
199200
}
201+
202+
func TestDeleteTraceflow_Tombstone(t *testing.T) {
203+
tfc := newController()
204+
205+
tf := &crdv1beta1.Traceflow{
206+
ObjectMeta: metav1.ObjectMeta{Name: "tf1", UID: "uid1"},
207+
Spec: crdv1beta1.TraceflowSpec{
208+
Source: crdv1beta1.Source{Namespace: "ns1", Pod: "pod1"},
209+
Destination: crdv1beta1.Destination{Namespace: "ns2", Pod: "pod2"},
210+
},
211+
Status: crdv1beta1.TraceflowStatus{
212+
DataplaneTag: 5,
213+
},
214+
}
215+
216+
// Simulate the tag being tracked as running.
217+
tfc.runningTraceflowsMutex.Lock()
218+
tfc.runningTraceflows[uint8(tf.Status.DataplaneTag)] = tf.Name
219+
tfc.runningTraceflowsMutex.Unlock()
220+
221+
// Deliver the delete as a tombstone, as the informer does after a watch reconnect.
222+
tfc.deleteTraceflow(cache.DeletedFinalStateUnknown{
223+
Key: tf.Name,
224+
Obj: tf,
225+
})
226+
227+
tfc.runningTraceflowsMutex.Lock()
228+
_, exists := tfc.runningTraceflows[uint8(tf.Status.DataplaneTag)]
229+
tfc.runningTraceflowsMutex.Unlock()
230+
assert.False(t, exists, "expected tag to be deallocated after tombstone delete")
231+
}

0 commit comments

Comments
 (0)