Skip to content

Commit

Permalink
Add plural IP fields to EndpointSpec
Browse files Browse the repository at this point in the history
...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/Set methods were added to EndpointSpec that handle the singular
fields. On Get, if the plural field is empty then retrieve the
singular field. On Set, if one of the specified IPs is IPv4 then set
the corresponding singular field.

Signed-off-by: Tom Pantelis <tompantelis@gmail.com>
  • Loading branch information
tpantelis committed Jan 15, 2025
1 parent b9e7095 commit 50a3b93
Show file tree
Hide file tree
Showing 6 changed files with 346 additions and 34 deletions.
48 changes: 48 additions & 0 deletions pkg/apis/submariner.io/v1/endpoint.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import (
"github.com/pkg/errors"
"github.com/submariner-io/admiral/pkg/resource"
"k8s.io/apimachinery/pkg/api/equality"
k8snet "k8s.io/utils/net"
)

func (ep *EndpointSpec) GetBackendPort(configName string, defaultValue int32) (int32, error) {
Expand Down Expand Up @@ -102,3 +103,50 @@ func (ep *EndpointSpec) hasSameBackendConfig(other *EndpointSpec) bool {

return equality.Semantic.DeepEqual(ep.BackendConfig, other.BackendConfig)
}

func fromArrayOrString(a []string, s string) []string {
if len(a) > 0 || s == "" {
return a
}

return []string{s}
}

func toIPArrayAndV4String(ips []string) ([]string, string) {
v4IP := ""
ipArray := make([]string, len(ips))

for i := range ips {
ipArray[i] = ips[i]

if k8snet.IsIPv4String(ips[i]) {
v4IP = ips[i]
}
}

return ipArray, v4IP
}

func (ep *EndpointSpec) GetHealthCheckIPs() []string {
return fromArrayOrString(ep.HealthCheckIPs, ep.HealthCheckIP)
}

func (ep *EndpointSpec) SetHealthCheckIPs(ips ...string) {
ep.HealthCheckIPs, ep.HealthCheckIP = toIPArrayAndV4String(ips)
}

func (ep *EndpointSpec) GetPublicIPs() []string {
return fromArrayOrString(ep.PublicIPs, ep.PublicIP)
}

func (ep *EndpointSpec) SetPublicIPs(ips ...string) {
ep.PublicIPs, ep.PublicIP = toIPArrayAndV4String(ips)
}

func (ep *EndpointSpec) GetPrivateIPs() []string {
return fromArrayOrString(ep.PrivateIPs, ep.PrivateIP)
}

func (ep *EndpointSpec) SetPrivateIPs(ips ...string) {
ep.PrivateIPs, ep.PrivateIP = toIPArrayAndV4String(ips)
}
209 changes: 209 additions & 0 deletions pkg/apis/submariner.io/v1/endpoint_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,20 @@ import (
v1 "github.com/submariner-io/submariner/pkg/apis/submariner.io/v1"
)

const (
ipV4Addr = "1.2.3.4"
ipV6Addr = "2001:db8:3333:4444:5555:6666:7777:8888"
)

var _ = Describe("EndpointSpec", func() {
Context("GenerateName", testGenerateName)
Context("Equals", testEquals)
Context("GetHealthCheckIPs", testGetHealthCheckIPs)
Context("SetHealthCheckIPs", testSetHealthCheckIPs)
Context("GetPublicIPs", testGetPublicIPs)
Context("SetPublicIPs", testSetPublicIPs)
Context("GetPrivateIPs", testGetPrivateIPs)
Context("SetPrivateIPs", testSetPrivateIPs)
})

func testGenerateName() {
Expand Down Expand Up @@ -138,3 +149,201 @@ func testEquals() {
})
})
}

//nolint:dupl // Similar to other functions but not duplicate.
func testGetHealthCheckIPs() {
When("HealthCheckIPs is non-empty and HealthCheckIP is empty", func() {
It("should return the HealthCheckIPs", func() {
Expect((&v1.EndpointSpec{
HealthCheckIPs: []string{"1.2.3.4", "5.6.7.8"},
}).GetHealthCheckIPs()).To(Equal([]string{"1.2.3.4", "5.6.7.8"}))
})
})

When("HealthCheckIPs is empty and HealthCheckIP is non-empty", func() {
It("should return the HealthCheckIP", func() {
Expect((&v1.EndpointSpec{
HealthCheckIP: "1.2.3.4",
}).GetHealthCheckIPs()).To(Equal([]string{"1.2.3.4"}))
})
})

When("HealthCheckIPs and HealthCheckIP are non-empty", func() {
It("should return the HealthCheckIPs", func() {
Expect((&v1.EndpointSpec{
HealthCheckIPs: []string{"1.2.3.4", "5.6.7.8"},
HealthCheckIP: "9.10.11.12",
}).GetHealthCheckIPs()).To(Equal([]string{"1.2.3.4", "5.6.7.8"}))
})
})

When("HealthCheckIPs and HealthCheckIP are empty", func() {
It("should return empty", func() {
Expect((&v1.EndpointSpec{}).GetHealthCheckIPs()).To(BeEmpty())
})
})
}

func testSetHealthCheckIPs() {
var spec *v1.EndpointSpec

BeforeEach(func() {
spec = &v1.EndpointSpec{}
})

When("a v4 address is specified", func() {
It("should set HealthCheckIPs and HealthCheckIP", func() {
spec.SetHealthCheckIPs(ipV4Addr)
Expect(spec.HealthCheckIPs).To(Equal([]string{ipV4Addr}))
Expect(spec.HealthCheckIP).To(Equal(ipV4Addr))
})
})

When("a v6 address is specified", func() {
It("should set only HealthCheckIPs", func() {
spec.SetHealthCheckIPs(ipV6Addr)
Expect(spec.HealthCheckIPs).To(Equal([]string{ipV6Addr}))
Expect(spec.HealthCheckIP).To(BeEmpty())
})
})

When("v4 and v6 addresses are specified", func() {
It("should set HealthCheckIPs and HealthCheckIP", func() {
spec.SetHealthCheckIPs(ipV4Addr, ipV6Addr)
Expect(spec.HealthCheckIPs).To(Equal([]string{ipV4Addr, ipV6Addr}))
Expect(spec.HealthCheckIP).To(Equal(ipV4Addr))
})
})
}

//nolint:dupl // Similar to other functions but not duplicate.
func testGetPublicIPs() {
When("PublicIPs is non-empty and PublicIP is empty", func() {
It("should return the PublicIPs", func() {
Expect((&v1.EndpointSpec{
PublicIPs: []string{"1.2.3.4", "5.6.7.8"},
}).GetPublicIPs()).To(Equal([]string{"1.2.3.4", "5.6.7.8"}))
})
})

When("PublicIPs is empty and PublicIP is non-empty", func() {
It("should return the PublicIP", func() {
Expect((&v1.EndpointSpec{
PublicIP: "1.2.3.4",
}).GetPublicIPs()).To(Equal([]string{"1.2.3.4"}))
})
})

When("PublicIPs and PublicIP are non-empty", func() {
It("should return the PublicIPs", func() {
Expect((&v1.EndpointSpec{
PublicIPs: []string{"1.2.3.4", "5.6.7.8"},
PublicIP: "9.10.11.12",
}).GetPublicIPs()).To(Equal([]string{"1.2.3.4", "5.6.7.8"}))
})
})

When("PublicIPs and PublicIP are empty", func() {
It("should return empty", func() {
Expect((&v1.EndpointSpec{}).GetPublicIPs()).To(BeEmpty())
})
})
}

func testSetPublicIPs() {
var spec *v1.EndpointSpec

BeforeEach(func() {
spec = &v1.EndpointSpec{}
})

When("a v4 address is specified", func() {
It("should set PublicIPs and PublicIP", func() {
spec.SetPublicIPs(ipV4Addr)
Expect(spec.PublicIPs).To(Equal([]string{ipV4Addr}))
Expect(spec.PublicIP).To(Equal(ipV4Addr))
})
})

When("a v6 address is specified", func() {
It("should set only PublicIPs", func() {
spec.SetPublicIPs(ipV6Addr)
Expect(spec.PublicIPs).To(Equal([]string{ipV6Addr}))
Expect(spec.PublicIP).To(BeEmpty())
})
})

When("v4 and v6 addresses are specified", func() {
It("should set PublicIPs and PublicIP", func() {
spec.SetPublicIPs(ipV4Addr, ipV6Addr)
Expect(spec.PublicIPs).To(Equal([]string{ipV4Addr, ipV6Addr}))
Expect(spec.PublicIP).To(Equal(ipV4Addr))
})
})
}

//nolint:dupl // Similar to other functions but not duplicate.
func testGetPrivateIPs() {
When("PrivateIPs is non-empty and PrivateIP is empty", func() {
It("should return the PrivateIPs", func() {
Expect((&v1.EndpointSpec{
PrivateIPs: []string{"1.2.3.4", "5.6.7.8"},
}).GetPrivateIPs()).To(Equal([]string{"1.2.3.4", "5.6.7.8"}))
})
})

When("PrivateIPs is empty and PrivateIP is non-empty", func() {
It("should return the PrivateIP", func() {
Expect((&v1.EndpointSpec{
PrivateIP: "1.2.3.4",
}).GetPrivateIPs()).To(Equal([]string{"1.2.3.4"}))
})
})

When("PrivateIPs and PrivateIP are non-empty", func() {
It("should return the PrivateIPs", func() {
Expect((&v1.EndpointSpec{
PrivateIPs: []string{"1.2.3.4", "5.6.7.8"},
PrivateIP: "9.10.11.12",
}).GetPrivateIPs()).To(Equal([]string{"1.2.3.4", "5.6.7.8"}))
})
})

When("PrivateIPs and PrivateIP are empty", func() {
It("should return empty", func() {
Expect((&v1.EndpointSpec{}).GetPrivateIPs()).To(BeEmpty())
})
})
}

func testSetPrivateIPs() {
var spec *v1.EndpointSpec

BeforeEach(func() {
spec = &v1.EndpointSpec{}
})

When("a v4 address is specified", func() {
It("should set PrivateIPs and PrivateIP", func() {
spec.SetPrivateIPs(ipV4Addr)
Expect(spec.PrivateIPs).To(Equal([]string{ipV4Addr}))
Expect(spec.PrivateIP).To(Equal(ipV4Addr))
})
})

When("a v6 address is specified", func() {
It("should set only PrivateIPs", func() {
spec.SetPrivateIPs(ipV6Addr)
Expect(spec.PrivateIPs).To(Equal([]string{ipV6Addr}))
Expect(spec.PrivateIP).To(BeEmpty())
})
})

When("v4 and v6 addresses are specified", func() {
It("should set PrivateIPs and PrivateIP", func() {
spec.SetPrivateIPs(ipV6Addr, ipV4Addr)
Expect(spec.PrivateIPs).To(Equal([]string{ipV6Addr, ipV4Addr}))
Expect(spec.PrivateIP).To(Equal(ipV4Addr))
})
})
}
36 changes: 17 additions & 19 deletions pkg/apis/submariner.io/v1/string_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,25 +26,23 @@ import (
v1 "github.com/submariner-io/submariner/pkg/apis/submariner.io/v1"
)

const expectedString = `{"metadata":{"creationTimestamp":null},"spec":{"cluster_id":"cluster-id","cable_name":` +
`"cable-1","hostname":"","subnets":["10.0.0.0/24","172.0.0.0/24"],"private_ip":"1.1.1.1",` +
`"public_ip":"","nat_enabled":false,"backend":""}}`

var _ = Describe("API v1", func() {
When("Endpoint String representation called", func() {
It("Should return a human readable string", func() {
endpoint := v1.Endpoint{
Spec: v1.EndpointSpec{
ClusterID: "cluster-id",
Subnets: []string{"10.0.0.0/24", "172.0.0.0/24"},
CableName: "cable-1",
PublicIP: "",
PrivateIP: "1.1.1.1",
},
}

Expect(endpoint.String()).To(Equal(expectedString))
})
var _ = Describe("Endpoint String", func() {
It("should return a human readable string", func() {
str := (&v1.Endpoint{
Spec: v1.EndpointSpec{
ClusterID: "east",
Subnets: []string{"10.0.0.0/24"},
CableName: "cable-1",
PublicIPs: []string{"1.1.1.1"},
PrivateIPs: []string{"2.2.2.2"},
},
}).String()

Expect(str).To(ContainSubstring("east"))
Expect(str).To(ContainSubstring("10.0.0.0/24"))
Expect(str).To(ContainSubstring("cable-1"))
Expect(str).To(ContainSubstring("1.1.1.1"))
Expect(str).To(ContainSubstring("2.2.2.2"))
})
})

Expand Down
19 changes: 14 additions & 5 deletions pkg/apis/submariner.io/v1/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,11 +79,20 @@ type EndpointSpec struct {
ClusterID string `json:"cluster_id"`
CableName string `json:"cable_name"`
// +optional
HealthCheckIP string `json:"healthCheckIP,omitempty"`
Hostname string `json:"hostname"`
Subnets []string `json:"subnets"`
PrivateIP string `json:"private_ip"`
PublicIP string `json:"public_ip"`
HealthCheckIP string `json:"healthCheckIP,omitempty"`
// +kubebuilder:validation:MaxItems:=2
// +optional
HealthCheckIPs []string `json:"healthCheckIPs,omitempty"`
Hostname string `json:"hostname"`
Subnets []string `json:"subnets"`
// +optional
PrivateIP string `json:"private_ip,omitempty"`
// +kubebuilder:validation:MaxItems:=2
PrivateIPs []string `json:"privateIPs,omitempty"`
// +optional
PublicIP string `json:"public_ip,omitempty"`
// +kubebuilder:validation:MaxItems:=2
PublicIPs []string `json:"publicIPs,omitempty"`
NATEnabled bool `json:"nat_enabled"`
Backend string `json:"backend"`
BackendConfig map[string]string `json:"backend_config,omitempty"`
Expand Down
15 changes: 15 additions & 0 deletions pkg/apis/submariner.io/v1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit 50a3b93

Please sign in to comment.