From 69489786007b94b1e87c1b0615d185b8a8379859 Mon Sep 17 00:00:00 2001 From: zbb88888 Date: Wed, 11 Feb 2026 16:09:19 +0800 Subject: [PATCH] fix: skip IPAM init for empty IP annotation and handle nil IPRangeList - Skip IPAM initialization when pod IP annotation is empty, log warning instead - Make IPRangeList.Len() nil-safe to prevent panic in GetSubnetIPRangeString - Add unit test for nil IPRangeList.Separate() behavior Signed-off-by: zbb88888 --- pkg/controller/init.go | 6 ++- pkg/ipam/ip_range_list.go | 24 +++++++-- pkg/ipam/ip_range_list_test.go | 92 ++++++++++++++++++++++++++++++++++ 3 files changed, 118 insertions(+), 4 deletions(-) diff --git a/pkg/controller/init.go b/pkg/controller/init.go index 28dc60bd967..3823d82c25b 100644 --- a/pkg/controller/init.go +++ b/pkg/controller/init.go @@ -452,9 +452,13 @@ func (c *Controller) InitIPAM() error { portName := ovs.PodNameToPortName(podName, pod.Namespace, podNet.ProviderName) ip := pod.Annotations[fmt.Sprintf(util.IPAddressAnnotationTemplate, podNet.ProviderName)] mac := pod.Annotations[fmt.Sprintf(util.MacAddressAnnotationTemplate, podNet.ProviderName)] + if ip == "" { + klog.Warningf("pod %s/%s has empty IP annotation for provider %s, skip IPAM init", pod.Namespace, podName, podNet.ProviderName) + continue + } _, _, _, err := c.ipam.GetStaticAddress(key, portName, ip, &mac, podNet.Subnet.Name, true) if err != nil { - klog.Errorf("failed to init pod %s.%s address %s: %v", podName, pod.Namespace, pod.Annotations[fmt.Sprintf(util.IPAddressAnnotationTemplate, podNet.ProviderName)], err) + klog.Errorf("failed to init pod %s.%s address %s: %v", podName, pod.Namespace, ip, err) } else { err = c.createOrUpdateIPCR(portName, podName, ip, mac, podNet.Subnet.Name, pod.Namespace, pod.Spec.NodeName, podType) if err != nil { diff --git a/pkg/ipam/ip_range_list.go b/pkg/ipam/ip_range_list.go index bd1b2c00af6..8b39fdb174d 100644 --- a/pkg/ipam/ip_range_list.go +++ b/pkg/ipam/ip_range_list.go @@ -77,6 +77,9 @@ func NewIPRangeListFrom(x ...string) (*IPRangeList, error) { } func (r *IPRangeList) Clone() *IPRangeList { + if r == nil { + return NewEmptyIPRangeList() + } ret := &IPRangeList{make([]*IPRange, r.Len())} for i := range r.ranges { ret.ranges[i] = r.ranges[i].Clone() @@ -85,10 +88,16 @@ func (r *IPRangeList) Clone() *IPRangeList { } func (r *IPRangeList) Len() int { + if r == nil { + return 0 + } return len(r.ranges) } func (r *IPRangeList) Count() internal.BigInt { + if r == nil { + return internal.BigInt{} + } var count internal.BigInt for _, v := range r.ranges { count = count.Add(v.Count()) @@ -97,13 +106,16 @@ func (r *IPRangeList) Count() internal.BigInt { } func (r *IPRangeList) At(i int) *IPRange { - if i < len(r.ranges) { - return r.ranges[i] + if r == nil || i < 0 || i >= len(r.ranges) { + return nil } - return nil + return r.ranges[i] } func (r *IPRangeList) Find(ip IP) (int, bool) { + if r == nil { + return 0, false + } return sort.Find(len(r.ranges), func(i int) int { if r.At(i).Start().GreaterThan(ip) { return -1 @@ -121,6 +133,9 @@ func (r *IPRangeList) Contains(ip IP) bool { } func (r *IPRangeList) Add(ip IP) bool { + if r == nil { + return false + } n, ok := r.Find(ip) if ok { return false @@ -145,6 +160,9 @@ func (r *IPRangeList) Add(ip IP) bool { } func (r *IPRangeList) Remove(ip IP) bool { + if r == nil { + return false + } n, ok := r.Find(ip) if !ok { return false diff --git a/pkg/ipam/ip_range_list_test.go b/pkg/ipam/ip_range_list_test.go index 9c491dc3c53..4d6204b2d3e 100644 --- a/pkg/ipam/ip_range_list_test.go +++ b/pkg/ipam/ip_range_list_test.go @@ -1388,3 +1388,95 @@ func TestIPRangeListToCIDRsEdgeCases(t *testing.T) { require.Equal(t, []string{"::/128"}, result) }) } + +func TestIPRangeList_NilReceiverSafety(t *testing.T) { + // Background: In single-stack subnets, V4Available or V6Available can be nil. + // All IPRangeList methods must handle nil receiver without panic. + var nilList *IPRangeList + ip, _ := NewIP("10.0.0.1") + + t.Run("Len", func(t *testing.T) { + require.NotPanics(t, func() { + require.Equal(t, 0, nilList.Len()) + }) + }) + + t.Run("Count", func(t *testing.T) { + require.NotPanics(t, func() { + require.Equal(t, "0", nilList.Count().String()) + }) + }) + + t.Run("Clone", func(t *testing.T) { + var result *IPRangeList + require.NotPanics(t, func() { + result = nilList.Clone() + }) + require.NotNil(t, result) + require.Equal(t, 0, result.Len()) + }) + + t.Run("At", func(t *testing.T) { + require.NotPanics(t, func() { + require.Nil(t, nilList.At(0)) + }) + }) + + t.Run("Find", func(t *testing.T) { + require.NotPanics(t, func() { + idx, found := nilList.Find(ip) + require.Equal(t, 0, idx) + require.False(t, found) + }) + }) + + t.Run("Contains", func(t *testing.T) { + require.NotPanics(t, func() { + require.False(t, nilList.Contains(ip)) + }) + }) + + t.Run("Add", func(t *testing.T) { + require.NotPanics(t, func() { + require.False(t, nilList.Add(ip)) + }) + }) + + t.Run("Remove", func(t *testing.T) { + require.NotPanics(t, func() { + require.False(t, nilList.Remove(ip)) + }) + }) + + t.Run("Separate", func(t *testing.T) { + var result *IPRangeList + require.NotPanics(t, func() { + result = nilList.Separate(nil) + }) + require.NotNil(t, result) + require.Equal(t, 0, result.Len()) + }) + + t.Run("String", func(t *testing.T) { + require.NotPanics(t, func() { + require.Equal(t, "", nilList.String()) + }) + }) + + t.Run("ToCIDRs", func(t *testing.T) { + require.NotPanics(t, func() { + cidrs, err := nilList.ToCIDRs() + require.NoError(t, err) + require.Nil(t, cidrs) + }) + }) +} + +func TestIPRangeListSeparate_WithEmptyList(t *testing.T) { + // An empty but non-nil list returns empty result immediately without using 'other' + emptyList := NewEmptyIPRangeList() + + result := emptyList.Separate(nil) + require.NotNil(t, result) + require.Equal(t, 0, result.Len()) +}