Skip to content

Commit e38182b

Browse files
committed
fix: review
Signed-off-by: Ashing Zheng <[email protected]>
1 parent 266f84b commit e38182b

File tree

2 files changed

+686
-85
lines changed

2 files changed

+686
-85
lines changed

pkg/utils/endpoints.go

Lines changed: 68 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -18,19 +18,25 @@
1818
package utils
1919

2020
import (
21+
"fmt"
22+
"net"
23+
2124
corev1 "k8s.io/api/core/v1"
2225
discoveryv1 "k8s.io/api/discovery/v1"
2326
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2427
"k8s.io/utils/ptr"
2528
)
2629

27-
// ConvertEndpointsToEndpointSlice converts a Kubernetes Endpoints object to an EndpointSlice object.
30+
// ConvertEndpointsToEndpointSlice converts a Kubernetes Endpoints object to one
31+
// or more EndpointSlice objects, supporting IPv4/IPv6 dual stack.
2832
// This function is used to provide backward compatibility for Kubernetes 1.18 clusters that don't
2933
// have EndpointSlice support but still use the older Endpoints API.
3034
//
3135
// The conversion follows these rules:
32-
// - Each Endpoints subset becomes a separate EndpointSlice
33-
// - Endpoint addresses are mapped to EndpointSlice endpoints
36+
// - Each Endpoints subset is split into separate IPv4 and IPv6 EndpointSlices
37+
// - Uses net.ParseIP for reliable address family detection instead of string matching
38+
// - IPv4 and IPv6 endpoints from the same subset are separated into different slices
39+
// - Naming convention: <svc-name>-<subset-index>-v4 / -v6
3440
// - Port information is preserved
3541
// - Ready state is mapped from Endpoints addresses vs notReadyAddresses
3642
//
@@ -41,127 +47,104 @@ func ConvertEndpointsToEndpointSlice(ep *corev1.Endpoints) []discoveryv1.Endpoin
4147
return nil
4248
}
4349

44-
//nolint:prealloc
4550
var endpointSlices []discoveryv1.EndpointSlice
4651

4752
// If there are no subsets, create an empty EndpointSlice
4853
if len(ep.Subsets) == 0 {
49-
endpointSlice := discoveryv1.EndpointSlice{
54+
endpointSlices = append(endpointSlices, discoveryv1.EndpointSlice{
5055
ObjectMeta: metav1.ObjectMeta{
51-
Name: ep.Name,
52-
Namespace: ep.Namespace,
53-
Labels: map[string]string{
54-
discoveryv1.LabelServiceName: ep.Name,
55-
},
56+
Name: ep.Name + "-v4", // Default to v4
57+
Namespace: ep.Namespace,
58+
Labels: map[string]string{discoveryv1.LabelServiceName: ep.Name},
59+
OwnerReferences: ep.OwnerReferences,
5660
},
57-
AddressType: discoveryv1.AddressTypeIPv4, // Default to IPv4
61+
AddressType: discoveryv1.AddressTypeIPv4,
5862
Ports: []discoveryv1.EndpointPort{},
5963
Endpoints: []discoveryv1.Endpoint{},
60-
}
61-
endpointSlices = append(endpointSlices, endpointSlice)
64+
})
6265
return endpointSlices
6366
}
6467

65-
// Convert each subset to an EndpointSlice
6668
for i, subset := range ep.Subsets {
6769
// Create ports array
6870
var ports []discoveryv1.EndpointPort
69-
for _, port := range subset.Ports {
70-
endpointPort := discoveryv1.EndpointPort{
71-
Name: &port.Name,
72-
Port: &port.Port,
73-
Protocol: &port.Protocol,
71+
for _, p := range subset.Ports {
72+
epPort := discoveryv1.EndpointPort{
73+
Port: &p.Port,
74+
Protocol: &p.Protocol,
7475
}
75-
ports = append(ports, endpointPort)
76+
if p.Name != "" {
77+
epPort.Name = &p.Name
78+
}
79+
ports = append(ports, epPort)
7680
}
7781

78-
// Create endpoints array from addresses (ready endpoints)
79-
var endpoints []discoveryv1.Endpoint
80-
for _, addr := range subset.Addresses {
81-
endpoint := discoveryv1.Endpoint{
82+
// Separate IPv4 and IPv6 addresses
83+
var (
84+
ipv4Endpoints []discoveryv1.Endpoint
85+
ipv6Endpoints []discoveryv1.Endpoint
86+
)
87+
buildEndpoint := func(addr corev1.EndpointAddress, ready bool) discoveryv1.Endpoint {
88+
e := discoveryv1.Endpoint{
8289
Addresses: []string{addr.IP},
8390
Conditions: discoveryv1.EndpointConditions{
84-
Ready: ptr.To(true), // Addresses in Endpoints.Addresses are ready
91+
Ready: ptr.To(ready),
8592
},
8693
}
87-
88-
// Add target ref if available
8994
if addr.TargetRef != nil {
90-
endpoint.TargetRef = addr.TargetRef
95+
e.TargetRef = addr.TargetRef
9196
}
92-
93-
// Add hostname if available
9497
if addr.Hostname != "" {
95-
endpoint.Hostname = &addr.Hostname
98+
e.Hostname = &addr.Hostname
9699
}
97-
98-
endpoints = append(endpoints, endpoint)
100+
return e
99101
}
100102

101-
// Add not ready addresses
102-
for _, addr := range subset.NotReadyAddresses {
103-
endpoint := discoveryv1.Endpoint{
104-
Addresses: []string{addr.IP},
105-
Conditions: discoveryv1.EndpointConditions{
106-
Ready: ptr.To(false), // NotReadyAddresses are not ready
107-
},
108-
}
109-
110-
// Add target ref if available
111-
if addr.TargetRef != nil {
112-
endpoint.TargetRef = addr.TargetRef
103+
// Process ready addresses
104+
for _, a := range subset.Addresses {
105+
if isIPv6(a.IP) {
106+
ipv6Endpoints = append(ipv6Endpoints, buildEndpoint(a, true))
107+
} else {
108+
ipv4Endpoints = append(ipv4Endpoints, buildEndpoint(a, true))
113109
}
114-
115-
// Add hostname if available
116-
if addr.Hostname != "" {
117-
endpoint.Hostname = &addr.Hostname
110+
}
111+
// Process not ready addresses
112+
for _, a := range subset.NotReadyAddresses {
113+
if isIPv6(a.IP) {
114+
ipv6Endpoints = append(ipv6Endpoints, buildEndpoint(a, false))
115+
} else {
116+
ipv4Endpoints = append(ipv4Endpoints, buildEndpoint(a, false))
118117
}
119-
120-
endpoints = append(endpoints, endpoint)
121118
}
122119

123-
// Determine address type based on first endpoint
124-
addressType := discoveryv1.AddressTypeIPv4
125-
if len(endpoints) > 0 && len(endpoints[0].Addresses) > 0 {
126-
// Simple IPv6 detection - if address contains colons, assume IPv6
127-
if containsColon(endpoints[0].Addresses[0]) {
128-
addressType = discoveryv1.AddressTypeIPv6
120+
// Create EndpointSlices for each address type
121+
makeSlice := func(suffix string, addrType discoveryv1.AddressType, eps []discoveryv1.Endpoint) discoveryv1.EndpointSlice {
122+
return discoveryv1.EndpointSlice{
123+
ObjectMeta: metav1.ObjectMeta{
124+
Name: fmt.Sprintf("%s-%d-%s", ep.Name, i, suffix),
125+
Namespace: ep.Namespace,
126+
Labels: map[string]string{discoveryv1.LabelServiceName: ep.Name},
127+
OwnerReferences: ep.OwnerReferences,
128+
},
129+
AddressType: addrType,
130+
Ports: ports,
131+
Endpoints: eps,
129132
}
130133
}
131134

132-
// Create EndpointSlice name with suffix if multiple subsets
133-
name := ep.Name
134-
if len(ep.Subsets) > 1 {
135-
name = ep.Name + "-" + string(rune('a'+i))
135+
if len(ipv4Endpoints) > 0 {
136+
endpointSlices = append(endpointSlices, makeSlice("v4", discoveryv1.AddressTypeIPv4, ipv4Endpoints))
136137
}
137-
138-
endpointSlice := discoveryv1.EndpointSlice{
139-
ObjectMeta: metav1.ObjectMeta{
140-
Name: name,
141-
Namespace: ep.Namespace,
142-
Labels: map[string]string{
143-
discoveryv1.LabelServiceName: ep.Name,
144-
},
145-
// Copy owner references if they exist
146-
OwnerReferences: ep.OwnerReferences,
147-
},
148-
AddressType: addressType,
149-
Ports: ports,
150-
Endpoints: endpoints,
138+
if len(ipv6Endpoints) > 0 {
139+
endpointSlices = append(endpointSlices, makeSlice("v6", discoveryv1.AddressTypeIPv6, ipv6Endpoints))
151140
}
152-
153-
endpointSlices = append(endpointSlices, endpointSlice)
154141
}
155142

156143
return endpointSlices
157144
}
158145

159-
// containsColon is a simple helper to detect IPv6 addresses
160-
func containsColon(addr string) bool {
161-
for _, char := range addr {
162-
if char == ':' {
163-
return true
164-
}
165-
}
166-
return false
146+
// isIPv6 uses net.ParseIP to determine if an IP address is IPv6
147+
func isIPv6(ip string) bool {
148+
parsed := net.ParseIP(ip)
149+
return parsed != nil && parsed.To4() == nil
167150
}

0 commit comments

Comments
 (0)