Skip to content

Commit 2c0e7b5

Browse files
tpantelisskitt
authored andcommitted
Add plural IP fields to EndpointSpec
...that mirror the singular HealthCheckIP, PrivateIP and PublicIP fields to support dual-stack addresses. The singular fields are deprecated and remain to support backwards compatibility and migration with prior versions. Going forward only the plural fields will be used. Get*IP/Add*IP methods were added to EndpointSpec that handle the singular fields. On Get for IPv4, if the plural field doesn't contain an IPv4 address then retrieve the singular field. On Set for IPv4 also set the corresponding singular field. Signed-off-by: Tom Pantelis <[email protected]>
1 parent fe74afa commit 2c0e7b5

File tree

6 files changed

+378
-34
lines changed

6 files changed

+378
-34
lines changed

pkg/apis/submariner.io/v1/endpoint.go

+58
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import (
2525
"github.com/pkg/errors"
2626
"github.com/submariner-io/admiral/pkg/resource"
2727
"k8s.io/apimachinery/pkg/api/equality"
28+
k8snet "k8s.io/utils/net"
2829
)
2930

3031
func (ep *EndpointSpec) GetBackendPort(configName string, defaultValue int32) (int32, error) {
@@ -102,3 +103,60 @@ func (ep *EndpointSpec) hasSameBackendConfig(other *EndpointSpec) bool {
102103

103104
return equality.Semantic.DeepEqual(ep.BackendConfig, other.BackendConfig)
104105
}
106+
107+
func getIPFrom(family k8snet.IPFamily, ips []string, ipv4Fallback string) string {
108+
for _, ip := range ips {
109+
if k8snet.IPFamilyOfString(ip) == family {
110+
return ip
111+
}
112+
}
113+
114+
if family == k8snet.IPv4 {
115+
return ipv4Fallback
116+
}
117+
118+
return ""
119+
}
120+
121+
func setIP(ips []string, ipv4Fallback, newIP string) ([]string, string) {
122+
family := k8snet.IPFamilyOfString(newIP)
123+
124+
if family == k8snet.IPv4 {
125+
ipv4Fallback = newIP
126+
}
127+
128+
for i := range ips {
129+
if k8snet.IPFamilyOfString(ips[i]) == family {
130+
ips[i] = newIP
131+
return ips, ipv4Fallback
132+
}
133+
}
134+
135+
ips = append(ips, newIP)
136+
137+
return ips, ipv4Fallback
138+
}
139+
140+
func (ep *EndpointSpec) GetHealthCheckIP(family k8snet.IPFamily) string {
141+
return getIPFrom(family, ep.HealthCheckIPs, ep.HealthCheckIP)
142+
}
143+
144+
func (ep *EndpointSpec) SetHealthCheckIP(ip string) {
145+
ep.HealthCheckIPs, ep.HealthCheckIP = setIP(ep.HealthCheckIPs, ep.HealthCheckIP, ip)
146+
}
147+
148+
func (ep *EndpointSpec) GetPublicIP(family k8snet.IPFamily) string {
149+
return getIPFrom(family, ep.PublicIPs, ep.PublicIP)
150+
}
151+
152+
func (ep *EndpointSpec) SetPublicIP(ip string) {
153+
ep.PublicIPs, ep.PublicIP = setIP(ep.PublicIPs, ep.PublicIP, ip)
154+
}
155+
156+
func (ep *EndpointSpec) GetPrivateIP(family k8snet.IPFamily) string {
157+
return getIPFrom(family, ep.PrivateIPs, ep.PrivateIP)
158+
}
159+
160+
func (ep *EndpointSpec) SetPrivateIP(ip string) {
161+
ep.PrivateIPs, ep.PrivateIP = setIP(ep.PrivateIPs, ep.PrivateIP, ip)
162+
}

pkg/apis/submariner.io/v1/endpoint_test.go

+229
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,23 @@ import (
2222
. "github.com/onsi/ginkgo/v2"
2323
. "github.com/onsi/gomega"
2424
v1 "github.com/submariner-io/submariner/pkg/apis/submariner.io/v1"
25+
k8snet "k8s.io/utils/net"
26+
)
27+
28+
const (
29+
ipV4Addr = "1.2.3.4"
30+
ipV6Addr = "2001:db8:3333:4444:5555:6666:7777:8888"
2531
)
2632

2733
var _ = Describe("EndpointSpec", func() {
2834
Context("GenerateName", testGenerateName)
2935
Context("Equals", testEquals)
36+
Context("GetHealthCheckIP", testGetHealthCheckIP)
37+
Context("SetHealthCheckIP", testSetHealthCheckIP)
38+
Context("GetPublicIP", testGetPublicIP)
39+
Context("SetPublicIP", testSetPublicIP)
40+
Context("GetPrivateIP", testGetPrivateIP)
41+
Context("SetPrivateIP", testSetPrivateIP)
3042
})
3143

3244
func testGenerateName() {
@@ -138,3 +150,220 @@ func testEquals() {
138150
})
139151
})
140152
}
153+
154+
func testGetIP(ipsSetter func(*v1.EndpointSpec, []string, string), ipsGetter func(*v1.EndpointSpec, k8snet.IPFamily) string) {
155+
var (
156+
spec *v1.EndpointSpec
157+
legacyIPv4IP string
158+
ips []string
159+
)
160+
161+
BeforeEach(func() {
162+
legacyIPv4IP = ""
163+
ips = []string{}
164+
})
165+
166+
JustBeforeEach(func() {
167+
spec = &v1.EndpointSpec{}
168+
ipsSetter(spec, ips, legacyIPv4IP)
169+
})
170+
171+
Context("IPv4", func() {
172+
When("an IPv4 address is present", func() {
173+
BeforeEach(func() {
174+
ips = []string{ipV6Addr, ipV4Addr}
175+
})
176+
177+
It("should return the address", func() {
178+
Expect(ipsGetter(spec, k8snet.IPv4)).To(Equal(ipV4Addr))
179+
})
180+
})
181+
182+
When("an IPv4 address is not present and the legacy IPv4 address is set", func() {
183+
BeforeEach(func() {
184+
ips = []string{ipV6Addr}
185+
legacyIPv4IP = ipV4Addr
186+
})
187+
188+
It("should return the legacy address", func() {
189+
Expect(ipsGetter(spec, k8snet.IPv4)).To(Equal(ipV4Addr))
190+
})
191+
})
192+
193+
When("an IPv4 address is not present and the legacy IPv4 address is not set", func() {
194+
It("should return empty string", func() {
195+
Expect(ipsGetter(spec, k8snet.IPv4)).To(BeEmpty())
196+
})
197+
})
198+
})
199+
200+
Context("IPv6", func() {
201+
When("an IPv6 address is present", func() {
202+
BeforeEach(func() {
203+
ips = []string{ipV4Addr, ipV6Addr}
204+
})
205+
206+
It("should return the address", func() {
207+
Expect(ipsGetter(spec, k8snet.IPv6)).To(Equal(ipV6Addr))
208+
})
209+
})
210+
211+
When("an IPv6 address is not present", func() {
212+
BeforeEach(func() {
213+
ips = []string{ipV4Addr}
214+
})
215+
216+
It("should return empty string", func() {
217+
Expect(ipsGetter(spec, k8snet.IPv6)).To(BeEmpty())
218+
})
219+
})
220+
})
221+
}
222+
223+
func testSetIP(initIPs func(*v1.EndpointSpec, []string), ipsSetter func(*v1.EndpointSpec, string),
224+
ipsGetter func(*v1.EndpointSpec) ([]string, string),
225+
) {
226+
var (
227+
spec *v1.EndpointSpec
228+
ipToSet string
229+
initialIPs []string
230+
)
231+
232+
BeforeEach(func() {
233+
spec = &v1.EndpointSpec{}
234+
initialIPs = []string{}
235+
ipToSet = ""
236+
})
237+
238+
JustBeforeEach(func() {
239+
initIPs(spec, initialIPs)
240+
ipsSetter(spec, ipToSet)
241+
})
242+
243+
verifyIPs := func(ips []string, legacyV4 string) {
244+
actualIPs, actualLegacy := ipsGetter(spec)
245+
Expect(actualIPs).To(Equal(ips))
246+
Expect(actualLegacy).To(Equal(legacyV4))
247+
}
248+
249+
Context("IPv4", func() {
250+
BeforeEach(func() {
251+
ipToSet = ipV4Addr
252+
})
253+
254+
When("no addresses are present", func() {
255+
It("should add the new address", func() {
256+
verifyIPs([]string{ipToSet}, ipToSet)
257+
})
258+
})
259+
260+
When("no IPv4 address is present", func() {
261+
BeforeEach(func() {
262+
initialIPs = []string{ipV6Addr}
263+
})
264+
265+
It("should add the new address", func() {
266+
verifyIPs([]string{ipV6Addr, ipToSet}, ipToSet)
267+
})
268+
})
269+
270+
When("an IPv4 address is already present", func() {
271+
BeforeEach(func() {
272+
initialIPs = []string{"11.22.33.44"}
273+
})
274+
275+
It("should update address", func() {
276+
verifyIPs([]string{ipToSet}, ipToSet)
277+
})
278+
})
279+
})
280+
281+
Context("IPv6", func() {
282+
BeforeEach(func() {
283+
ipToSet = ipV6Addr
284+
})
285+
286+
When("no addresses are present", func() {
287+
It("should add the new address", func() {
288+
verifyIPs([]string{ipToSet}, "")
289+
})
290+
})
291+
292+
When("no IPv6 address is present", func() {
293+
BeforeEach(func() {
294+
initialIPs = []string{ipV4Addr}
295+
})
296+
297+
It("should add the new address", func() {
298+
verifyIPs([]string{ipV4Addr, ipToSet}, "")
299+
})
300+
})
301+
302+
When("an IPv6 address is already present", func() {
303+
BeforeEach(func() {
304+
initialIPs = []string{"1234:cb9:3333:4444:5555:6666:7777:8888"}
305+
})
306+
307+
It("should update address", func() {
308+
verifyIPs([]string{ipToSet}, "")
309+
})
310+
})
311+
})
312+
}
313+
314+
func testGetHealthCheckIP() {
315+
testGetIP(func(s *v1.EndpointSpec, ips []string, ipv4IP string) {
316+
s.HealthCheckIPs = ips
317+
s.HealthCheckIP = ipv4IP
318+
}, func(s *v1.EndpointSpec, family k8snet.IPFamily) string {
319+
return s.GetHealthCheckIP(family)
320+
})
321+
}
322+
323+
func testSetHealthCheckIP() {
324+
testSetIP(func(s *v1.EndpointSpec, ips []string) {
325+
s.HealthCheckIPs = ips
326+
}, func(s *v1.EndpointSpec, ip string) {
327+
s.SetHealthCheckIP(ip)
328+
}, func(s *v1.EndpointSpec) ([]string, string) {
329+
return s.HealthCheckIPs, s.HealthCheckIP
330+
})
331+
}
332+
333+
func testGetPublicIP() {
334+
testGetIP(func(s *v1.EndpointSpec, ips []string, ipv4IP string) {
335+
s.PublicIPs = ips
336+
s.PublicIP = ipv4IP
337+
}, func(s *v1.EndpointSpec, family k8snet.IPFamily) string {
338+
return s.GetPublicIP(family)
339+
})
340+
}
341+
342+
func testSetPublicIP() {
343+
testSetIP(func(s *v1.EndpointSpec, ips []string) {
344+
s.PublicIPs = ips
345+
}, func(s *v1.EndpointSpec, ip string) {
346+
s.SetPublicIP(ip)
347+
}, func(s *v1.EndpointSpec) ([]string, string) {
348+
return s.PublicIPs, s.PublicIP
349+
})
350+
}
351+
352+
func testGetPrivateIP() {
353+
testGetIP(func(s *v1.EndpointSpec, ips []string, ipv4IP string) {
354+
s.PrivateIPs = ips
355+
s.PrivateIP = ipv4IP
356+
}, func(s *v1.EndpointSpec, family k8snet.IPFamily) string {
357+
return s.GetPrivateIP(family)
358+
})
359+
}
360+
361+
func testSetPrivateIP() {
362+
testSetIP(func(s *v1.EndpointSpec, ips []string) {
363+
s.PrivateIPs = ips
364+
}, func(s *v1.EndpointSpec, ip string) {
365+
s.SetPrivateIP(ip)
366+
}, func(s *v1.EndpointSpec) ([]string, string) {
367+
return s.PrivateIPs, s.PrivateIP
368+
})
369+
}

pkg/apis/submariner.io/v1/string_test.go

+17-19
Original file line numberDiff line numberDiff line change
@@ -26,25 +26,23 @@ import (
2626
v1 "github.com/submariner-io/submariner/pkg/apis/submariner.io/v1"
2727
)
2828

29-
const expectedString = `{"metadata":{"creationTimestamp":null},"spec":{"cluster_id":"cluster-id","cable_name":` +
30-
`"cable-1","hostname":"","subnets":["10.0.0.0/24","172.0.0.0/24"],"private_ip":"1.1.1.1",` +
31-
`"public_ip":"","nat_enabled":false,"backend":""}}`
32-
33-
var _ = Describe("API v1", func() {
34-
When("Endpoint String representation called", func() {
35-
It("Should return a human readable string", func() {
36-
endpoint := v1.Endpoint{
37-
Spec: v1.EndpointSpec{
38-
ClusterID: "cluster-id",
39-
Subnets: []string{"10.0.0.0/24", "172.0.0.0/24"},
40-
CableName: "cable-1",
41-
PublicIP: "",
42-
PrivateIP: "1.1.1.1",
43-
},
44-
}
45-
46-
Expect(endpoint.String()).To(Equal(expectedString))
47-
})
29+
var _ = Describe("Endpoint String", func() {
30+
It("should return a human readable string", func() {
31+
str := (&v1.Endpoint{
32+
Spec: v1.EndpointSpec{
33+
ClusterID: "east",
34+
Subnets: []string{"10.0.0.0/24"},
35+
CableName: "cable-1",
36+
PublicIPs: []string{"1.1.1.1"},
37+
PrivateIPs: []string{"2.2.2.2"},
38+
},
39+
}).String()
40+
41+
Expect(str).To(ContainSubstring("east"))
42+
Expect(str).To(ContainSubstring("10.0.0.0/24"))
43+
Expect(str).To(ContainSubstring("cable-1"))
44+
Expect(str).To(ContainSubstring("1.1.1.1"))
45+
Expect(str).To(ContainSubstring("2.2.2.2"))
4846
})
4947
})
5048

pkg/apis/submariner.io/v1/types.go

+16-5
Original file line numberDiff line numberDiff line change
@@ -79,11 +79,22 @@ type EndpointSpec struct {
7979
ClusterID string `json:"cluster_id"`
8080
CableName string `json:"cable_name"`
8181
// +optional
82-
HealthCheckIP string `json:"healthCheckIP,omitempty"`
83-
Hostname string `json:"hostname"`
84-
Subnets []string `json:"subnets"`
85-
PrivateIP string `json:"private_ip"`
86-
PublicIP string `json:"public_ip"`
82+
HealthCheckIP string `json:"healthCheckIP,omitempty"`
83+
// +kubebuilder:validation:MaxItems:=2
84+
// +optional
85+
HealthCheckIPs []string `json:"healthCheckIPs,omitempty"`
86+
Hostname string `json:"hostname"`
87+
Subnets []string `json:"subnets"`
88+
// +optional
89+
PrivateIP string `json:"private_ip,omitempty"`
90+
// +kubebuilder:validation:MaxItems:=2
91+
// +optional
92+
PrivateIPs []string `json:"privateIPs,omitempty"`
93+
// +optional
94+
PublicIP string `json:"public_ip,omitempty"`
95+
// +kubebuilder:validation:MaxItems:=2
96+
// +optional
97+
PublicIPs []string `json:"publicIPs,omitempty"`
8798
NATEnabled bool `json:"nat_enabled"`
8899
Backend string `json:"backend"`
89100
BackendConfig map[string]string `json:"backend_config,omitempty"`

0 commit comments

Comments
 (0)