Skip to content

Commit 721d667

Browse files
authored
Merge pull request kubernetes#128305 from adrianmoisey/cidr_release_on_node_delete
Ensure that a node's CIDR isn't released until the node is deleted
2 parents 0a62f0f + 4d2f3ed commit 721d667

File tree

2 files changed

+122
-10
lines changed

2 files changed

+122
-10
lines changed

pkg/controller/nodeipam/ipam/range_allocator.go

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -145,16 +145,22 @@ func NewCIDRRangeAllocator(ctx context.Context, client clientset.Interface, node
145145
DeleteFunc: func(obj interface{}) {
146146
// The informer cache no longer has the object, and since Node doesn't have a finalizer,
147147
// we don't see the Update with DeletionTimestamp != 0.
148-
// TODO: instead of executing the operation directly in the handler, build a small cache with key node.Name
149-
// and value PodCIDRs use ReleaseCIDR on the reconcile loop so we can retry on `ReleaseCIDR` failures.
150-
if err := ra.ReleaseCIDR(logger, obj.(*v1.Node)); err != nil {
151-
utilruntime.HandleError(fmt.Errorf("error while processing CIDR Release: %w", err))
148+
node, ok := obj.(*v1.Node)
149+
if !ok {
150+
tombstone, ok := obj.(cache.DeletedFinalStateUnknown)
151+
if !ok {
152+
utilruntime.HandleError(fmt.Errorf("unexpected object type: %v", obj))
153+
return
154+
}
155+
node, ok = tombstone.Obj.(*v1.Node)
156+
if !ok {
157+
utilruntime.HandleError(fmt.Errorf("unexpected object types: %v", obj))
158+
return
159+
}
152160
}
153-
// IndexerInformer uses a delta nodeQueue, therefore for deletes we have to use this
154-
// key function.
155-
key, err := cache.DeletionHandlingMetaNamespaceKeyFunc(obj)
156-
if err == nil {
157-
ra.queue.Add(key)
161+
if err := ra.ReleaseCIDR(logger, node); err != nil {
162+
utilruntime.HandleError(fmt.Errorf("error while processing CIDR Release: %w", err))
163+
158164
}
159165
},
160166
})
@@ -270,7 +276,7 @@ func (r *rangeAllocator) syncNode(ctx context.Context, key string) error {
270276
// Check the DeletionTimestamp to determine if object is under deletion.
271277
if !node.DeletionTimestamp.IsZero() {
272278
logger.V(3).Info("node is being deleted", "node", key)
273-
return r.ReleaseCIDR(logger, node)
279+
return nil
274280
}
275281
return r.AllocateOrOccupyCIDR(ctx, node)
276282
}

pkg/controller/nodeipam/ipam/range_allocator_test.go

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -841,3 +841,109 @@ func TestReleaseCIDRSuccess(t *testing.T) {
841841
testFunc(tc)
842842
}
843843
}
844+
func TestNodeDeletionReleaseCIDR(t *testing.T) {
845+
_, clusterCIDRv4, err := netutils.ParseCIDRSloppy("10.10.0.0/16")
846+
if err != nil {
847+
t.Fatalf("unexpected error: %v", err)
848+
}
849+
850+
_, allocatedCIDR, err := netutils.ParseCIDRSloppy("10.10.0.0/24")
851+
if err != nil {
852+
t.Fatalf("unexpected error: %v", err)
853+
}
854+
testCases := []struct {
855+
description string
856+
nodeKey string
857+
existingNodes []*v1.Node
858+
shouldReleaseCIDR bool
859+
}{
860+
{
861+
description: "Regular node not under deletion",
862+
nodeKey: "node0",
863+
existingNodes: []*v1.Node{
864+
{
865+
ObjectMeta: metav1.ObjectMeta{
866+
Name: "node0",
867+
},
868+
Spec: v1.NodeSpec{
869+
PodCIDR: allocatedCIDR.String(),
870+
PodCIDRs: []string{allocatedCIDR.String()}},
871+
},
872+
},
873+
shouldReleaseCIDR: false,
874+
},
875+
{
876+
description: "Node under deletion",
877+
nodeKey: "node0",
878+
existingNodes: []*v1.Node{
879+
{
880+
ObjectMeta: metav1.ObjectMeta{
881+
Name: "node0",
882+
DeletionTimestamp: &metav1.Time{Time: time.Now()},
883+
},
884+
Spec: v1.NodeSpec{
885+
PodCIDR: allocatedCIDR.String(),
886+
PodCIDRs: []string{allocatedCIDR.String()}},
887+
},
888+
},
889+
shouldReleaseCIDR: false,
890+
},
891+
{
892+
description: "Node deleted",
893+
nodeKey: "node0",
894+
existingNodes: []*v1.Node{},
895+
shouldReleaseCIDR: true,
896+
},
897+
}
898+
899+
for _, tc := range testCases {
900+
t.Run(tc.description, func(t *testing.T) {
901+
allocatorParams := CIDRAllocatorParams{
902+
ClusterCIDRs: []*net.IPNet{clusterCIDRv4}, ServiceCIDR: nil,
903+
SecondaryServiceCIDR: nil,
904+
NodeCIDRMaskSizes: []int{24},
905+
}
906+
907+
fakeNodeHandler := &testutil.FakeNodeHandler{
908+
Existing: tc.existingNodes,
909+
Clientset: fake.NewSimpleClientset(),
910+
}
911+
_, tCtx := ktesting.NewTestContext(t)
912+
913+
fakeNodeInformer := test.FakeNodeInformer(fakeNodeHandler)
914+
nodeList, err := fakeNodeHandler.List(tCtx, metav1.ListOptions{})
915+
if err != nil {
916+
t.Fatalf("Failed to get list of nodes %v", err)
917+
}
918+
allocator, err := NewCIDRRangeAllocator(tCtx, fakeNodeHandler, fakeNodeInformer, allocatorParams, nodeList)
919+
if err != nil {
920+
t.Fatalf("failed to create CIDRRangeAllocator: %v", err)
921+
}
922+
rangeAllocator, ok := allocator.(*rangeAllocator)
923+
if !ok {
924+
t.Fatalf("found non-default implementation of CIDRAllocator")
925+
}
926+
rangeAllocator.nodesSynced = test.AlwaysReady
927+
rangeAllocator.recorder = testutil.NewFakeRecorder()
928+
929+
rangeAllocator.syncNode(tCtx, tc.nodeKey)
930+
931+
if len(rangeAllocator.cidrSets) != 1 {
932+
t.Fatalf("Expected a single cidrSet, but got %d", len(rangeAllocator.cidrSets))
933+
}
934+
935+
// if the allocated CIDR was released we expect the nextAllocated CIDR to be the same
936+
nextCIDR, err := rangeAllocator.cidrSets[0].AllocateNext()
937+
if err != nil {
938+
t.Fatalf("unexpected error trying to allocate next CIDR: %v", err)
939+
}
940+
expectedCIDR := "10.10.1.0/24" // existing allocated is 10.0.0.0/24
941+
if tc.shouldReleaseCIDR {
942+
expectedCIDR = allocatedCIDR.String() // if cidr was released we expect to reuse it. ie: 10.0.0.0/24
943+
}
944+
if nextCIDR.String() != expectedCIDR {
945+
t.Fatalf("Expected CIDR %s to be allocated next, but got: %v", expectedCIDR, nextCIDR.String())
946+
}
947+
})
948+
}
949+
}

0 commit comments

Comments
 (0)