Skip to content

Commit af2ef31

Browse files
HadrienPattepippolo84
authored andcommitted
ipam: Migrate operator-side IP-keyed maps to netip.Addr
Relates to cilium#24246 Followup to: * cilium#45790 * cilium#45647 * cilium#45508 * cilium#45495 * cilium#45395 * cilium#45260 This PR continues the netip migration by converting the operator-side `Node`'s `ipsMarkedForRelease` and `ipReleaseStatus` map keys from `string` to `netip.Addr`, along with the `addressCoveredByPrefix` helper. The internal `ipsToRelease` slice in `handleIPRelease` is also flipped to `[]netip.Addr`. As with previous PRs, there are now conversions at the boundary with the CRD types (`Status.IPAM.ReleaseIPs` map keys, `ReleaseAction.IPsToRelease`). Signed-off-by: Hadrien Patte <hadrien.patte@datadoghq.com>
1 parent 621593e commit af2ef31

4 files changed

Lines changed: 58 additions & 47 deletions

File tree

operator/pkg/ipam/nodemanager/node.go

Lines changed: 46 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import (
2929
"github.com/cilium/cilium/pkg/lock"
3030
"github.com/cilium/cilium/pkg/logging"
3131
"github.com/cilium/cilium/pkg/logging/logfields"
32+
cslices "github.com/cilium/cilium/pkg/slices"
3233
"github.com/cilium/cilium/pkg/time"
3334
"github.com/cilium/cilium/pkg/trigger"
3435
)
@@ -152,14 +153,14 @@ type ipAllocAttrs struct {
152153
// Excess IP address from a cilium node would be marked for release only after a delay
153154
// configured by excess-ip-release-delay flag. ipsMarkedForRelease tracks the IP and the
154155
// timestamp at which it was marked for release.
155-
ipsMarkedForRelease map[string]time.Time
156+
ipsMarkedForRelease map[netip.Addr]time.Time
156157

157158
// ipReleaseStatus tracks the state for every IP address considered for release.
158159
// IPAMMarkForRelease : Marked for Release
159160
// IPAMReadyForRelease : Acknowledged as safe to release by agent
160161
// IPAMDoNotRelease : Release request denied by agent
161162
// IPAMReleased : IP released by the operator
162-
ipReleaseStatus map[string]string
163+
ipReleaseStatus map[netip.Addr]string
163164
}
164165

165166
// Statistics represent the IP allocation statistics of a node
@@ -611,11 +612,15 @@ func (n *Node) buildPoolAllocated(node *v2.CiliumNode) []ipamTypes.IPAMPoolAlloc
611612
// Here we need to apply a reverse logic to only advertise as /32 CIDRs in the pool
612613
// regular secondary addresses (or the ENI primary IP when using UsePrimaryAddress)
613614
// and not addresses that are already being advertised through a /28 CIDR.
614-
for _, addr := range eni.Addresses {
615-
if addressCoveredByPrefix(addr, prefixes) {
615+
for _, addrStr := range eni.Addresses {
616+
parsed, err := netip.ParseAddr(addrStr)
617+
if err != nil {
618+
continue
619+
}
620+
if addressCoveredByPrefix(parsed, prefixes) {
616621
continue
617622
}
618-
cidrs = append(cidrs, ipamTypes.IPAMCIDR(addr+"/32"))
623+
cidrs = append(cidrs, ipamTypes.IPAMCIDR(addrStr+"/32"))
619624
}
620625
}
621626

@@ -631,18 +636,11 @@ func (n *Node) buildPoolAllocated(node *v2.CiliumNode) []ipamTypes.IPAMPoolAlloc
631636
}
632637
}
633638

634-
// addressCoveredByPrefix returns true if the given IP address string falls
639+
// addressCoveredByPrefix returns true if the given IP address falls
635640
// within any of the provided prefixes.
636-
func addressCoveredByPrefix(addr string, prefixes []netip.Prefix) bool {
637-
if len(prefixes) == 0 {
638-
return false
639-
}
640-
ip, err := netip.ParseAddr(addr)
641-
if err != nil {
642-
return false
643-
}
641+
func addressCoveredByPrefix(addr netip.Addr, prefixes []netip.Prefix) bool {
644642
for _, p := range prefixes {
645-
if p.Contains(ip) {
643+
if p.Contains(addr) {
646644
return true
647645
}
648646
}
@@ -854,12 +852,12 @@ func (n *Node) determineMaintenanceAction() (*maintenanceAction, error) {
854852
func (n *Node) removeStaleReleaseIPs() {
855853
n.mutex.Lock()
856854
defer n.mutex.Unlock()
857-
for ip, status := range n.ipv4Alloc.ipReleaseStatus {
855+
for addr, status := range n.ipv4Alloc.ipReleaseStatus {
858856
if status != ipamOption.IPAMReleased {
859857
continue
860858
}
861-
if _, ok := n.resource.Status.IPAM.ReleaseIPs[ip]; !ok {
862-
delete(n.ipv4Alloc.ipReleaseStatus, ip)
859+
if _, ok := n.resource.Status.IPAM.ReleaseIPs[addr.String()]; !ok {
860+
delete(n.ipv4Alloc.ipReleaseStatus, addr)
863861
}
864862
}
865863
}
@@ -876,33 +874,37 @@ func (n *Node) abortNoLongerExcessIPs(excessMap map[string]bool) {
876874
if excessMap[ip] {
877875
continue
878876
}
877+
addr, err := netip.ParseAddr(ip)
878+
if err != nil {
879+
continue
880+
}
879881
// Handshake can be aborted from every state except 'released'
880882
// 'released' state is removed by the agent once the IP has been removed from ciliumnode's IPAM pool as well.
881883
// But if the IP is back in the pool, we need to remove it from the release status map.
882884
if status == ipamOption.IPAMReleased {
883885
// Check if the IP is back in the pool despite being marked as released
884886
if _, ok := n.resource.Spec.IPAM.Pool[ip]; ok {
885887
delete(n.resource.Status.IPAM.ReleaseIPs, ip)
886-
delete(n.ipv4Alloc.ipsMarkedForRelease, ip)
887-
delete(n.ipv4Alloc.ipReleaseStatus, ip)
888+
delete(n.ipv4Alloc.ipsMarkedForRelease, addr)
889+
delete(n.ipv4Alloc.ipReleaseStatus, addr)
888890
}
889891

890892
// If it's still released and not in the pool, we don't need to do anything
891893
continue
892894
}
893895

894-
if status, ok := n.ipv4Alloc.ipReleaseStatus[ip]; ok && status != ipamOption.IPAMReleased {
895-
delete(n.ipv4Alloc.ipsMarkedForRelease, ip)
896-
delete(n.ipv4Alloc.ipReleaseStatus, ip)
896+
if status, ok := n.ipv4Alloc.ipReleaseStatus[addr]; ok && status != ipamOption.IPAMReleased {
897+
delete(n.ipv4Alloc.ipsMarkedForRelease, addr)
898+
delete(n.ipv4Alloc.ipReleaseStatus, addr)
897899
}
898900
}
899901
}
900902

901903
// handleIPReleaseResponse handles IPs agent has already responded to
902904
// caller must hold mutex lock
903-
func (n *Node) handleIPReleaseResponse(markedIP string, ipsToRelease *[]string) bool {
905+
func (n *Node) handleIPReleaseResponse(markedIP netip.Addr, ipsToRelease *[]netip.Addr) bool {
904906
if n.resource.Status.IPAM.ReleaseIPs != nil {
905-
if status, ok := n.resource.Status.IPAM.ReleaseIPs[markedIP]; ok {
907+
if status, ok := n.resource.Status.IPAM.ReleaseIPs[markedIP.String()]; ok {
906908
switch status {
907909
case ipamOption.IPAMReadyForRelease:
908910
*ipsToRelease = append(*ipsToRelease, markedIP)
@@ -932,29 +934,33 @@ func (n *Node) handleIPReleaseResponse(markedIP string, ipsToRelease *[]string)
932934
//
933935
// Handshake would be aborted if there are new allocations and the node doesn't have IPs in excess anymore.
934936
func (n *Node) handleIPRelease(ctx context.Context, a *maintenanceAction) (instanceMutated bool, err error) {
935-
var ipsToMark []string
936-
var ipsToRelease []string
937+
var ipsToMark []netip.Addr
938+
var ipsToRelease []netip.Addr
937939

938940
n.mutex.Lock()
939941

940942
// Update timestamps for IPs from this iteration
941943
releaseTS := time.Now()
942944
if a.release != nil && a.release.IPsToRelease != nil {
943945
for _, ip := range a.release.IPsToRelease {
944-
if _, ok := n.ipv4Alloc.ipsMarkedForRelease[ip]; !ok {
945-
n.ipv4Alloc.ipsMarkedForRelease[ip] = releaseTS
946+
addr, err := netip.ParseAddr(ip)
947+
if err != nil {
948+
continue
949+
}
950+
if _, ok := n.ipv4Alloc.ipsMarkedForRelease[addr]; !ok {
951+
n.ipv4Alloc.ipsMarkedForRelease[addr] = releaseTS
946952
}
947953
}
948954
}
949955

950956
if n.ipv4Alloc.ipsMarkedForRelease == nil || a.release == nil || len(a.release.IPsToRelease) == 0 {
951957
// Resetting ipsMarkedForRelease if there are no IPs to release in this iteration
952-
n.ipv4Alloc.ipsMarkedForRelease = make(map[string]time.Time)
958+
n.ipv4Alloc.ipsMarkedForRelease = make(map[netip.Addr]time.Time)
953959
}
954960

955961
for markedIP, ts := range n.ipv4Alloc.ipsMarkedForRelease {
956962
// Determine which IPs are still marked for release.
957-
stillMarkedForRelease := slices.Contains(a.release.IPsToRelease, markedIP)
963+
stillMarkedForRelease := slices.Contains(a.release.IPsToRelease, markedIP.String())
958964
if !stillMarkedForRelease {
959965
// n.determineMaintenanceAction() only returns the IPs on the interface with maximum number of IPs that
960966
// can be freed up. If the selected interface changes or if this IP is not excess anymore, remove entry
@@ -975,12 +981,12 @@ func (n *Node) handleIPRelease(ctx context.Context, a *maintenanceAction) (insta
975981
ipsToMark = append(ipsToMark, markedIP)
976982
}
977983

978-
for _, ip := range ipsToMark {
984+
for _, addr := range ipsToMark {
979985
n.logger.Load().Debug(
980986
"Marking IP for release",
981-
logfields.IPAddr, ip,
987+
logfields.IPAddr, addr,
982988
)
983-
n.ipv4Alloc.ipReleaseStatus[ip] = ipamOption.IPAMMarkForRelease
989+
n.ipv4Alloc.ipReleaseStatus[addr] = ipamOption.IPAMMarkForRelease
984990
}
985991
n.mutex.Unlock()
986992

@@ -995,7 +1001,7 @@ func (n *Node) handleIPRelease(ctx context.Context, a *maintenanceAction) (insta
9951001
n.abortNoLongerExcessIPs(excessMap)
9961002

9971003
if len(ipsToRelease) > 0 {
998-
a.release.IPsToRelease = ipsToRelease
1004+
a.release.IPsToRelease = cslices.Map(ipsToRelease, netip.Addr.String)
9991005

10001006
nodeStats := n.Stats()
10011007

@@ -1031,9 +1037,9 @@ func (n *Node) handleIPRelease(ctx context.Context, a *maintenanceAction) (insta
10311037
n.manager.metricsAPI.AddIPRelease(string(a.release.PoolID), int64(len(a.release.IPsToRelease)))
10321038
// Remove the IPs from ipsMarkedForRelease
10331039
n.mutex.Lock()
1034-
for _, ip := range ipsToRelease {
1035-
delete(n.ipv4Alloc.ipsMarkedForRelease, ip)
1036-
n.ipv4Alloc.ipReleaseStatus[ip] = ipamOption.IPAMReleased
1040+
for _, addr := range ipsToRelease {
1041+
delete(n.ipv4Alloc.ipsMarkedForRelease, addr)
1042+
n.ipv4Alloc.ipReleaseStatus[addr] = ipamOption.IPAMReleased
10371043
}
10381044
n.mutex.Unlock()
10391045
return true, nil
@@ -1188,7 +1194,8 @@ func (n *Node) PopulateIPReleaseStatus(node *v2.CiliumNode) {
11881194
n.mutex.Lock()
11891195
defer n.mutex.Unlock()
11901196
releaseStatus := make(map[string]ipamTypes.IPReleaseStatus)
1191-
for ip, status := range n.ipv4Alloc.ipReleaseStatus {
1197+
for addr, status := range n.ipv4Alloc.ipReleaseStatus {
1198+
ip := addr.String()
11921199
if existingStatus, ok := node.Status.IPAM.ReleaseIPs[ip]; ok && status == ipamOption.IPAMMarkForRelease {
11931200
// retain status if agent already responded to this IP
11941201
if existingStatus == ipamOption.IPAMReadyForRelease || existingStatus == ipamOption.IPAMDoNotRelease {

operator/pkg/ipam/nodemanager/node_manager.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"context"
1010
"fmt"
1111
"log/slog"
12+
"net/netip"
1213
"sort"
1314

1415
"golang.org/x/sync/semaphore"
@@ -288,8 +289,8 @@ func (n *NodeManager) Upsert(resource *v2.CiliumNode) {
288289
manager: n,
289290
logLimiter: logging.NewLimiter(10*time.Second, 3), // 1 log / 10 secs, burst of 3
290291
ipv4Alloc: ipAllocAttrs{
291-
ipsMarkedForRelease: make(map[string]time.Time),
292-
ipReleaseStatus: make(map[string]string),
292+
ipsMarkedForRelease: make(map[netip.Addr]time.Time),
293+
ipReleaseStatus: make(map[netip.Addr]string),
293294
},
294295
excessIPReleaseDelay: time.Duration(n.excessIPReleaseDelay) * time.Second,
295296
}

operator/pkg/ipam/nodemanager/node_manager_test.go

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"context"
88
"fmt"
99
"log/slog"
10+
"net/netip"
1011
"slices"
1112
"sync"
1213
"testing"
@@ -127,7 +128,7 @@ func (n *nodeOperationsMock) AllocateIPs(ctx context.Context, allocation *Alloca
127128
n.allocator.allocatedIPs += allocation.IPv4.AvailableForAllocation
128129
for range allocation.IPv4.AvailableForAllocation {
129130
n.allocator.ipGenerator++
130-
n.allocatedIPs = append(n.allocatedIPs, fmt.Sprintf("%d", n.allocator.ipGenerator))
131+
n.allocatedIPs = append(n.allocatedIPs, fmt.Sprintf("10.0.%d.%d", n.allocator.ipGenerator/256, n.allocator.ipGenerator%256))
131132
}
132133
n.allocator.mutex.Unlock()
133134
n.mutex.Unlock()
@@ -717,7 +718,8 @@ func TestNodeManagerAbortReleaseIPReassignment(t *testing.T) {
717718
node.resource.Status.IPAM.ReleaseIPs[releasedIP] = ipamOption.IPAMReleased
718719

719720
// Also mark it as released in the internal ipReleaseStatus map
720-
node.ipv4Alloc.ipReleaseStatus[releasedIP] = ipamOption.IPAMReleased
721+
releasedAddr := netip.MustParseAddr(releasedIP)
722+
node.ipv4Alloc.ipReleaseStatus[releasedAddr] = ipamOption.IPAMReleased
721723

722724
// Normally at this point, the agent would see the IP is released and remove it from Status.IPAM.ReleaseIPs
723725
// But before that happens, simulate the IP being reassigned back to the pool
@@ -743,8 +745,8 @@ func TestNodeManagerAbortReleaseIPReassignment(t *testing.T) {
743745
node.mutex.Lock()
744746
defer node.mutex.Unlock()
745747

746-
_, inReleaseStatus := node.ipv4Alloc.ipReleaseStatus[releasedIP]
747-
_, inMarkedForRelease := node.ipv4Alloc.ipsMarkedForRelease[releasedIP]
748+
_, inReleaseStatus := node.ipv4Alloc.ipReleaseStatus[releasedAddr]
749+
_, inMarkedForRelease := node.ipv4Alloc.ipsMarkedForRelease[releasedAddr]
748750
_, inReleaseIPs := node.resource.Status.IPAM.ReleaseIPs[releasedIP]
749751

750752
return !inReleaseStatus && !inMarkedForRelease && !inReleaseIPs

operator/pkg/ipam/nodemanager/node_test.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
package nodemanager
55

66
import (
7+
"net/netip"
78
"testing"
89

910
"github.com/cilium/hive/hivetest"
@@ -103,8 +104,8 @@ func TestSyncToAPIServerForNonExistingNode(t *testing.T) {
103104
},
104105
logLimiter: logging.NewLimiter(10*time.Second, 3), // 1 log / 10 secs, burst of 3
105106
ipv4Alloc: ipAllocAttrs{
106-
ipsMarkedForRelease: make(map[string]time.Time),
107-
ipReleaseStatus: make(map[string]string),
107+
ipsMarkedForRelease: make(map[netip.Addr]time.Time),
108+
ipReleaseStatus: make(map[netip.Addr]string),
108109
},
109110
resource: newCiliumNode("test-node", 0, 0, 0),
110111
ops: &nodeOperationsMock{},

0 commit comments

Comments
 (0)