Merge pull request #139233 from rahulbabu95/automated-cherry-pick-of-#138736-upstream-release-1.36

Automated cherry pick of #138736: fix: avoid panic on services with empty IPFamilies
This commit is contained in:
Kubernetes Prow Robot 2026-06-08 16:34:10 +05:30 committed by GitHub
commit cd90f1a01c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 95 additions and 1 deletions

View file

@ -220,8 +220,22 @@ func (e *Controller) Run(ctx context.Context, workers int) {
func podToEndpointAddressForService(svc *v1.Service, pod *v1.Pod) (*v1.EndpointAddress, error) {
var endpointIP string
ipFamily := v1.IPv4Protocol
wantIPv6 := svc.Spec.IPFamilies[0] == v1.IPv6Protocol
// IPFamilies is expected to be populated by apiserver defaulting, but
// some services may reach this controller with an empty IPFamilies via
// watch events. Infer the family from ClusterIP or
// pod IP so we never panic on IPFamilies[0].
if len(svc.Spec.IPFamilies) > 0 {
ipFamily = svc.Spec.IPFamilies[0]
} else if len(svc.Spec.ClusterIP) > 0 && svc.Spec.ClusterIP != v1.ClusterIPNone {
if utilnet.IsIPv6String(svc.Spec.ClusterIP) {
ipFamily = v1.IPv6Protocol
}
} else if utilnet.IsIPv6String(pod.Status.PodIP) {
ipFamily = v1.IPv6Protocol
}
wantIPv6 := ipFamily == v1.IPv6Protocol
// Find an IP that matches the family. We parse and restringify the IP in case the
// value on the Pod is in an irregular format.

View file

@ -3224,3 +3224,83 @@ func TestSyncEndpointsAddDeletePorts(t *testing.T) {
t.Fatalf("incorrect endpoints after deleting first port:\n%s", diff)
}
}
func TestPodToEndpointAddressForServiceEmptyIPFamilies(t *testing.T) {
testCases := []struct {
name string
clusterIP string
podIPs []v1.PodIP
podIP string
wantErr bool
wantFamily v1.IPFamily
}{
{
name: "headful IPv4, IPv4 pod",
clusterIP: "10.0.0.1",
podIPs: []v1.PodIP{{IP: "10.244.0.1"}},
wantFamily: v1.IPv4Protocol,
},
{
name: "headful IPv6, IPv6 pod",
clusterIP: "fd00::1",
podIPs: []v1.PodIP{{IP: "fd00::10"}},
wantFamily: v1.IPv6Protocol,
},
{
name: "headful IPv4, no matching pod IP",
clusterIP: "10.0.0.1",
podIPs: []v1.PodIP{{IP: "fd00::10"}},
wantErr: true,
},
{
name: "headless, IPv4 pod",
clusterIP: v1.ClusterIPNone,
podIPs: []v1.PodIP{{IP: "10.244.0.1"}},
podIP: "10.244.0.1",
wantFamily: v1.IPv4Protocol,
},
{
name: "headless, IPv6 pod",
clusterIP: v1.ClusterIPNone,
podIPs: []v1.PodIP{{IP: "fd00::10"}},
podIP: "fd00::10",
wantFamily: v1.IPv6Protocol,
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
svc := &v1.Service{
ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "bar"},
Spec: v1.ServiceSpec{
// Intentionally leave IPFamilies empty.
ClusterIP: tc.clusterIP,
},
}
pod := &v1.Pod{
ObjectMeta: metav1.ObjectMeta{Name: "foo-pod", Namespace: "bar", UID: "uid-1"},
Spec: v1.PodSpec{NodeName: "node-1"},
Status: v1.PodStatus{PodIP: tc.podIP, PodIPs: tc.podIPs},
}
addr, err := podToEndpointAddressForService(svc, pod)
if tc.wantErr {
if err == nil {
t.Fatalf("expected error but got addr=%v", addr)
}
return
}
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if addr == nil {
t.Fatal("expected an address but got nil")
}
isV6 := utilnet.IsIPv6String(addr.IP)
wantV6 := tc.wantFamily == v1.IPv6Protocol
if isV6 != wantV6 {
t.Errorf("got IP %q (IPv6=%v), want family %v", addr.IP, isV6, tc.wantFamily)
}
})
}
}