@@ -154,8 +154,84 @@ func (n *Node) PrepareIPRelease(excessIPs int, scopedLog *slog.Logger) *ipam.Rel
154154 for _ , eniId := range slices .Sorted (maps .Keys (n .enis )) {
155155 e := n .enis [eniId ]
156156
157- // IP release for prefixes is not currently supported. Will skip releasing from this ENI
158- if len (e .Prefixes ) > 0 {
157+ // Ignore if the ENI is not managed by Cilium
158+ if e .IsExcludedBySpec (n .k8sObj .Spec .ENI ) {
159+ continue
160+ }
161+ ipPrefixes := e .Prefixes
162+ ipsOnENI := e .Addresses
163+ usedIPs := n .k8sObj .Status .IPAM .Used
164+
165+ matchedIPs := []string {}
166+ // Returns the first ENI with either IPPrefixes/secondary IPs to release instead of
167+ // looking for an ENI with max IPPrefixes/secondary IPs to release for faster and
168+ // lower latency when early ENIs are eligible.
169+ if len (ipPrefixes ) > 0 {
170+ scopedLog .Debug (
171+ "Considering ENI for IPPrefix release" ,
172+ fieldEniID , e .ID ,
173+ logfields .NeedIndex , * n .k8sObj .Spec .ENI .FirstInterfaceIndex ,
174+ logfields .Index , e .Number ,
175+ logfields .NumAddresses , len (e .Addresses ),
176+ logfields .LenPrefixes , len (ipPrefixes ),
177+ logfields .ExcessIPs , excessIPs ,
178+ )
179+
180+ unusedIPPrefixes := []string {}
181+ if excessIPs >= option .ENIPDBlockSizeIPv4 {
182+ // Identify unused IP prefixes to release
183+ for _ , prefix := range ipPrefixes {
184+ prefixAddr , err := netip .ParsePrefix (prefix )
185+ if err != nil {
186+ continue
187+ }
188+
189+ found := false
190+ for ip := range usedIPs {
191+ if prefixAddr .Contains (netip .MustParseAddr (ip )) {
192+ found = true
193+ break
194+ }
195+ }
196+ if ! found {
197+ unusedIPPrefixes = append (unusedIPPrefixes , prefix )
198+ for _ , ipStr := range e .Addresses {
199+ ip := netip .MustParseAddr (ipStr )
200+ if prefixAddr .Contains (ip ) {
201+ matchedIPs = append (matchedIPs , ipStr )
202+ }
203+ }
204+ // Reduce excessIPs with option.ENIPDBlockSizeIPv4 number of IPs after adding a prefix
205+ excessIPs = excessIPs - option .ENIPDBlockSizeIPv4
206+ }
207+
208+ if excessIPs < option .ENIPDBlockSizeIPv4 {
209+ break
210+ }
211+ }
212+ }
213+
214+ secondaryIPs := getIndividualIPs (ipPrefixes , e .Addresses )
215+ if len (unusedIPPrefixes ) > 0 || len (secondaryIPs ) > 0 {
216+ r .InterfaceID = eniId
217+ r .PoolID = ipamTypes .PoolID (e .Subnet .ID )
218+ r .IPPrefixesToRelease = unusedIPPrefixes
219+ if len (secondaryIPs ) > 0 {
220+ secondaryIPs = getUnusedIPs (usedIPs , secondaryIPs , e .IP )
221+ maxReleaseOnENI := min (excessIPs , len (secondaryIPs ))
222+ matchedIPs = append (matchedIPs , secondaryIPs [:maxReleaseOnENI ]... )
223+ }
224+ scopedLog .Debug (
225+ "ENI has unused secondary IPs and/or IPPrefixes that can be released" ,
226+ fieldEniID , e .ID ,
227+ logfields .Prefix , unusedIPPrefixes ,
228+ logfields .IPAddrs , matchedIPs ,
229+ )
230+ r .IPsToRelease = matchedIPs
231+ // Return since we have either IPPrefixes/secondary IPs to release
232+ return r
233+ }
234+ // Look for next ENI if we do not have an ENI with either a Prefix/secondary IP to release
159235 continue
160236 }
161237 scopedLog .Debug (
@@ -166,22 +242,9 @@ func (n *Node) PrepareIPRelease(excessIPs int, scopedLog *slog.Logger) *ipam.Rel
166242 logfields .NumAddresses , len (e .Addresses ),
167243 )
168244
169- if e .IsExcludedBySpec (n .k8sObj .Spec .ENI ) {
170- continue
171- }
172-
173245 // Count free IP addresses on this ENI
174- ipsOnENI := n .k8sObj .Status .ENI .ENIs [e .ID ].Addresses
175- freeIpsOnENI := []string {}
176- for _ , ip := range ipsOnENI {
177- _ , ipUsed := n .k8sObj .Status .IPAM .Used [ip ]
178- // exclude primary IPs
179- if ! ipUsed && ip != e .IP {
180- freeIpsOnENI = append (freeIpsOnENI , ip )
181- }
182- }
246+ freeIpsOnENI := getUnusedIPs (usedIPs , ipsOnENI , e .IP )
183247 freeOnENICount := len (freeIpsOnENI )
184-
185248 if freeOnENICount <= 0 {
186249 continue
187250 }
@@ -193,7 +256,6 @@ func (n *Node) PrepareIPRelease(excessIPs int, scopedLog *slog.Logger) *ipam.Rel
193256 logfields .FreeOnENICount , freeOnENICount ,
194257 )
195258 maxReleaseOnENI := min (freeOnENICount , excessIPs )
196-
197259 firstENIWithFreeIPFound := r .IPsToRelease == nil
198260 eniWithMoreFreeIPsFound := maxReleaseOnENI > len (r .IPsToRelease )
199261 // Select the ENI with the most addresses available for release
@@ -203,12 +265,62 @@ func (n *Node) PrepareIPRelease(excessIPs int, scopedLog *slog.Logger) *ipam.Rel
203265 r .IPsToRelease = freeIpsOnENI [:maxReleaseOnENI ]
204266 }
205267 }
206-
207268 return r
208269}
209270
271+ // ReleaseIPPrefixes performs the ENI IPPrefixes release operation
272+ func (n * Node ) ReleaseIPPrefixes (ctx context.Context , r * ipam.ReleaseAction ) error {
273+ if err := n .manager .api .UnassignENIPrefixes (ctx , r .InterfaceID , r .IPPrefixesToRelease ); err != nil {
274+ return err
275+ }
276+
277+ return nil
278+
279+ }
280+
281+ // Get Unused Individual IPs
282+ func getUnusedIPs (usedIPs ipamTypes.AllocationMap , ipAddresses []string , primaryIP string ) (unusedIPs []string ) {
283+ for _ , ipStr := range ipAddresses {
284+ _ , usedIP := usedIPs [ipStr ]
285+ if ! usedIP && ipStr != primaryIP {
286+ unusedIPs = append (unusedIPs , ipStr )
287+ }
288+ }
289+ return unusedIPs
290+ }
291+
292+ // Get Individual IPs that do not belong to any IPPrefix
293+ func getIndividualIPs (ipPrefixes , ipAddresses []string ) (individualIPs []string ) {
294+ for _ , ipStr := range ipAddresses {
295+ matched := false
296+ ip := netip .MustParseAddr (ipStr )
297+ for _ , prefix := range ipPrefixes {
298+ prefixAddr , err := netip .ParsePrefix (prefix )
299+ if err != nil {
300+ continue
301+ }
302+ if prefixAddr .Contains (ip ) {
303+ matched = true
304+ break
305+ }
306+ }
307+ if ! matched {
308+ individualIPs = append (individualIPs , ipStr )
309+ }
310+ }
311+ return individualIPs
312+ }
313+
210314// ReleaseIPs performs the ENI IP release operation
211315func (n * Node ) ReleaseIPs (ctx context.Context , r * ipam.ReleaseAction ) error {
316+ // Filter IPs that do not belong to any IPPrefix
317+ if len (r .IPPrefixesToRelease ) > 0 {
318+ r .IPsToRelease = getIndividualIPs (r .IPPrefixesToRelease , r .IPsToRelease )
319+ }
320+
321+ if len (r .IPsToRelease ) <= 0 {
322+ return nil
323+ }
212324 if err := n .manager .api .UnassignPrivateIpAddresses (ctx , r .InterfaceID , r .IPsToRelease ); err != nil {
213325 return err
214326 }
@@ -822,58 +934,6 @@ func (n *Node) IsPrefixDelegated() bool {
822934 return true
823935}
824936
825- // GetUsedIPWithPrefixes returns the total number of used IPs on the node including the prefixes allocated.
826- // A prefix is considered as used if there is at least one allocated IP from that prefix. All IPs from a used prefix
827- // are included in the count returned.
828- func (n * Node ) GetUsedIPWithPrefixes () int {
829- var usedIps int
830- eniPrefixes := make (map [string ][]netip.Prefix )
831-
832- // Populate ENI -> Prefix mapping
833- for eniName , eni := range n .k8sObj .Status .ENI .ENIs {
834- var prefixes []netip.Prefix
835- for _ , pfx := range eni .Prefixes {
836- ipNet , err := netip .ParsePrefix (pfx )
837- if err != nil {
838- continue
839- }
840- prefixes = append (prefixes , ipNet )
841- }
842- eniPrefixes [eniName ] = prefixes
843- }
844- usedPfx := make (map [netip.Prefix ]bool )
845- for ip , resource := range n .k8sObj .Status .IPAM .Used {
846- // Fetch prefixes on this IP's ENI
847- prefixNetworks , exists := eniPrefixes [resource .Resource ]
848- if ! exists {
849- continue
850- }
851- var prefixBased bool
852- var pfx netip.Prefix
853- addr , err := netip .ParseAddr (ip )
854- if err != nil {
855- continue
856- }
857- // Check if the IP is from any of the prefixes attached to this ENI
858- for _ , ipNet := range prefixNetworks {
859- if ipNet .Contains (addr ) {
860- prefixBased = true
861- pfx = ipNet
862- break
863- }
864- }
865- if prefixBased {
866- if ! usedPfx [pfx ] {
867- usedIps = usedIps + option .ENIPDBlockSizeIPv4
868- usedPfx [pfx ] = true
869- }
870- } else {
871- usedIps ++
872- }
873- }
874- return usedIps
875- }
876-
877937// getEffectiveIPLimits computing the effective number of available addresses on the ENI
878938// based on limits (which includes any left over prefix delegation capacity), as well as
879939// just the left over prefix delegation capacity.
0 commit comments