@@ -289,6 +289,71 @@ var _ = framework.Describe("[group:subnet]", func() {
289289 }
290290 })
291291
292+ framework .ConformanceIt ("should allow pod with fixed IP or IP pool in excludeIPs when available IPs is 0" , func () {
293+ ginkgo .By ("Creating a small subnet with very limited IP range" )
294+ var smallCIDR string
295+ var excludeIPs []string
296+ var usableIPs []string
297+
298+ switch f .ClusterIPFamily {
299+ case "ipv4" :
300+ smallCIDR = "192.168.200.0/30"
301+ excludeIPs = []string {"192.168.200.1" , "192.168.200.2" }
302+ usableIPs = []string {"192.168.200.2" }
303+ case "ipv6" :
304+ smallCIDR = "fd00:192:168:200::/126"
305+ excludeIPs = []string {"fd00:192:168:200::1" , "fd00:192:168:200::2" }
306+ usableIPs = []string {"fd00:192:168:200::2" }
307+ case "dual" :
308+ smallCIDR = "192.168.200.0/30,fd00:192:168:200::/126"
309+ excludeIPs = []string {"192.168.200.1" , "192.168.200.2" , "fd00:192:168:200::1" , "fd00:192:168:200::2" }
310+ usableIPs = []string {"192.168.200.2" , "fd00:192:168:200::2" }
311+ }
312+
313+ subnetName = "small-subnet-" + framework .RandomSuffix ()
314+ ginkgo .By (fmt .Sprintf ("Creating small subnet %s with exclude IPs %v" , subnetName , excludeIPs ))
315+ smallSubnet := framework .MakeSubnet (subnetName , "" , smallCIDR , "" , "" , "" , excludeIPs , nil , []string {namespaceName })
316+ smallSubnet = subnetClient .CreateSync (smallSubnet )
317+
318+ ginkgo .By ("Verifying available IPs is 0 after excluding the only usable IPs" )
319+ framework .ExpectZero (smallSubnet .Status .V4AvailableIPs + smallSubnet .Status .V6AvailableIPs )
320+
321+ // Test cases: both fixed IP and IP pool annotations
322+ testCases := []struct {
323+ name string
324+ annotationKey string
325+ annotationValue string
326+ }{
327+ {
328+ name : "fix ip" ,
329+ annotationKey : util .IPAddressAnnotation ,
330+ annotationValue : strings .Join (usableIPs , "," ),
331+ },
332+ {
333+ name : "fix ip pool" ,
334+ annotationKey : util .IPPoolAnnotation ,
335+ annotationValue : strings .Join (usableIPs , "," ),
336+ },
337+ }
338+
339+ for _ , tc := range testCases {
340+ ginkgo .By (fmt .Sprintf ("Creating pod with %s annotation that matches excludeIPs" , tc .name ))
341+ podName = fmt .Sprintf ("pod-%s-%s" , strings .ReplaceAll (tc .name , " " , "-" ), framework .RandomSuffix ())
342+ annotations := map [string ]string {
343+ tc .annotationKey : tc .annotationValue ,
344+ }
345+ cmd := []string {"sleep" , "infinity" }
346+ pod := framework .MakePrivilegedPod (namespaceName , podName , nil , annotations , f .KubeOVNImage , cmd , nil )
347+ pod = podClient .CreateSync (pod )
348+
349+ ginkgo .By (fmt .Sprintf ("Verifying pod gets the %s IPs despite availableIPs being 0" , tc .name ))
350+ framework .ExpectHaveKeyWithValue (pod .Annotations , tc .annotationKey , tc .annotationValue )
351+
352+ ginkgo .By (fmt .Sprintf ("Cleaning up test pod for %s" , tc .name ))
353+ podClient .DeleteSync (podName )
354+ }
355+ })
356+
292357 framework .ConformanceIt ("should create subnet with centralized gateway" , func () {
293358 ginkgo .By ("Getting nodes" )
294359 nodes , err := e2enode .GetReadySchedulableNodes (context .Background (), cs )
0 commit comments