Skip to content

Commit

Permalink
support for IPv6 load balancer address
Browse files Browse the repository at this point in the history
  • Loading branch information
JochemTSR authored and JochemTSR committed Mar 25, 2024
1 parent b6a2dec commit 375fdf0
Show file tree
Hide file tree
Showing 7 changed files with 209 additions and 7 deletions.
7 changes: 7 additions & 0 deletions api/v1beta1/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,13 @@ type LoadBalancerSpec struct {

// Region contains the name of the HCloud location the load balancer is running.
Region Region `json:"region,omitempty"`

// UseIPv6Endpoint defines whether to use the LoadBalancer's IPv6 address as
// the cluster endpoint instead of IPv4. This is useful if nodes are provisioned
// without IPv4 address. Defaults to 'false'.
// +optional
// +kubebuilder:default=false
UseIPv6Endpoint bool `json:"useIPv6Endpoint,omitempty"`
}

// LoadBalancerServiceSpec defines a Loadbalancer Target.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,13 @@ spec:
- lb21
- lb31
type: string
useIPv6Endpoint:
default: false
description: UseIPv6Endpoint defines whether to use the LoadBalancer's
IPv6 address as the cluster endpoint instead of IPv4. This is
useful if nodes are provisioned without IPv4 address. Defaults
to 'false'.
type: boolean
type: object
controlPlaneRegions:
description: ControlPlaneRegion consists of a list of HCloud Regions
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,13 @@ spec:
- lb21
- lb31
type: string
useIPv6Endpoint:
default: false
description: UseIPv6Endpoint defines whether to use the
LoadBalancer's IPv6 address as the cluster endpoint
instead of IPv4. This is useful if nodes are provisioned
without IPv4 address. Defaults to 'false'.
type: boolean
type: object
controlPlaneRegions:
description: ControlPlaneRegion consists of a list of HCloud
Expand Down
7 changes: 4 additions & 3 deletions controllers/controllers_suite_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -154,9 +154,10 @@ func getDefaultHetznerClusterSpec() infrav1.HetznerClusterSpec {
Protocol: "tcp",
},
},
Port: 6443,
Region: "fsn1",
Type: "lb11",
Port: 6443,
Region: "fsn1",
Type: "lb11",
UseIPv6Endpoint: false,
},
ControlPlaneEndpoint: &clusterv1.APIEndpoint{},
ControlPlaneRegions: []infrav1.Region{"fsn1"},
Expand Down
9 changes: 7 additions & 2 deletions controllers/hetznercluster_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -243,8 +243,13 @@ func (r *HetznerClusterReconciler) reconcileNormal(ctx context.Context, clusterS

func processControlPlaneEndpoint(hetznerCluster *infrav1.HetznerCluster) {
if hetznerCluster.Spec.ControlPlaneLoadBalancer.Enabled {
if hetznerCluster.Status.ControlPlaneLoadBalancer.IPv4 != "<nil>" {
defaultHost := hetznerCluster.Status.ControlPlaneLoadBalancer.IPv4
ip := hetznerCluster.Status.ControlPlaneLoadBalancer.IPv4
if hetznerCluster.Spec.ControlPlaneLoadBalancer.UseIPv6Endpoint {
ip = hetznerCluster.Status.ControlPlaneLoadBalancer.IPv6
}

if ip != "<nil>" {
defaultHost := ip
defaultPort := int32(hetznerCluster.Spec.ControlPlaneLoadBalancer.Port)

if hetznerCluster.Spec.ControlPlaneEndpoint == nil {
Expand Down
174 changes: 174 additions & 0 deletions controllers/hetznercluster_controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1208,4 +1208,178 @@ func TestSetControlPlaneEndpoint(t *testing.T) {
t.Fatalf("return value should be true")
}
})

t.Run("return false if load balancer is enabled with UseIPv6Endpoint and IPv6 is 'nil'. ControlPlaneEndpoint should not change", func(t *testing.T) {
hetznerCluster := &infrav1.HetznerCluster{
Spec: infrav1.HetznerClusterSpec{
ControlPlaneLoadBalancer: infrav1.LoadBalancerSpec{
UseIPv6Endpoint: true,
Enabled: true,
},
ControlPlaneEndpoint: nil,
},
Status: infrav1.HetznerClusterStatus{
ControlPlaneLoadBalancer: &infrav1.LoadBalancerStatus{
IPv4: "xyz",
IPv6: "<nil>",
},
},
}

processControlPlaneEndpoint(hetznerCluster)

if hetznerCluster.Spec.ControlPlaneEndpoint != nil {
t.Fatalf("ControlPlaneEndpoint should not change. It should remain nil")
}

if hetznerCluster.Status.Ready != false {
t.Fatalf("return value should be false")
}

if !conditions.Has(hetznerCluster, infrav1.ControlPlaneEndpointSetCondition) {
t.Fatalf("ControlPlaneEndpointSetCondition should exist")
}

condition := conditions.Get(hetznerCluster, infrav1.ControlPlaneEndpointSetCondition)
if condition.Status != corev1.ConditionFalse {
t.Fatalf("condition status should be false")
}
})

t.Run("return true if load balancer is enabled with UseIPv6Endpoint, IPv6 is not nil and ControlPlaneEndpoint is nil. Values of ControlPlaneEndpoint.Host and ControlPlaneEndpoint.Port will get updated", func(t *testing.T) {
hetznerCluster := &infrav1.HetznerCluster{
Spec: infrav1.HetznerClusterSpec{
ControlPlaneLoadBalancer: infrav1.LoadBalancerSpec{
UseIPv6Endpoint: true,
Enabled: true,
Port: 11,
},
ControlPlaneEndpoint: nil,
},
Status: infrav1.HetznerClusterStatus{
ControlPlaneLoadBalancer: &infrav1.LoadBalancerStatus{
IPv6: "abc",
},
},
}

processControlPlaneEndpoint(hetznerCluster)

if hetznerCluster.Spec.ControlPlaneEndpoint.Host != "abc" {
t.Fatalf("Wrong value for Host set. Got: %s, Want: 'abc'", hetznerCluster.Spec.ControlPlaneEndpoint.Host)
}

if hetznerCluster.Spec.ControlPlaneEndpoint.Port != 11 {
t.Fatalf("Wrong value for Port set. Got: %d, Want: 11", hetznerCluster.Spec.ControlPlaneEndpoint.Port)
}

if hetznerCluster.Status.Ready != true {
t.Fatalf("return value should be true")
}
})

t.Run("return true if load balancer is enabled with UseIPv6Endopint, IPv6 is not nil, ControlPlaneEndpoint.Host is an empty string and ControlPlaneEndpoint.Port is 0. Values of ControlPlaneEndpoint.Host and ControlPlaneEndpoint.Port should update", func(t *testing.T) {
hetznerCluster := &infrav1.HetznerCluster{
Spec: infrav1.HetznerClusterSpec{
ControlPlaneLoadBalancer: infrav1.LoadBalancerSpec{
UseIPv6Endpoint: true,
Enabled: true,
Port: 11,
},
ControlPlaneEndpoint: &clusterv1.APIEndpoint{
Host: "",
Port: 0,
},
},
Status: infrav1.HetznerClusterStatus{
ControlPlaneLoadBalancer: &infrav1.LoadBalancerStatus{
IPv6: "abc",
},
},
}

processControlPlaneEndpoint(hetznerCluster)

if hetznerCluster.Spec.ControlPlaneEndpoint.Host != "abc" {
t.Fatalf("Wrong value for Host set. Got: %s, Want: 'abc'", hetznerCluster.Spec.ControlPlaneEndpoint.Host)
}

if hetznerCluster.Spec.ControlPlaneEndpoint.Port != 11 {
t.Fatalf("Wrong value for Port set. Got: %d, Want: 11", hetznerCluster.Spec.ControlPlaneEndpoint.Port)
}

if hetznerCluster.Status.Ready != true {
t.Fatalf("return value should be true")
}
})

t.Run("return true if load balancer is enabled with UseIPv6Endpoint, IPv6 is not nil, ControlPlaneEndpoint.Host is an empty string and ControlPlaneEndpoint.Port is 21. Value of ControlPlaneEndpoint.Host will change and ControlPlaneEndpoint.Port should remain same", func(t *testing.T) {
hetznerCluster := &infrav1.HetznerCluster{
Spec: infrav1.HetznerClusterSpec{
ControlPlaneLoadBalancer: infrav1.LoadBalancerSpec{
UseIPv6Endpoint: true,
Enabled: true,
Port: 11,
},
ControlPlaneEndpoint: &clusterv1.APIEndpoint{
Host: "",
Port: 21,
},
},
Status: infrav1.HetznerClusterStatus{
ControlPlaneLoadBalancer: &infrav1.LoadBalancerStatus{
IPv6: "abc",
},
},
}

processControlPlaneEndpoint(hetznerCluster)

if hetznerCluster.Spec.ControlPlaneEndpoint.Host != "abc" {
t.Fatalf("Wrong value for Host set. Got: %s, Want: 'abc'", hetznerCluster.Spec.ControlPlaneEndpoint.Host)
}

if hetznerCluster.Spec.ControlPlaneEndpoint.Port != 21 {
t.Fatalf("Wrong value for Port set. Got: %d, Want: 21", hetznerCluster.Spec.ControlPlaneEndpoint.Port)
}

if hetznerCluster.Status.Ready != true {
t.Fatalf("return value should be true")
}
})

t.Run("return true if load balancer is enabled with UseIPv6Endpoint, IPv6 is not nil, ControlPlaneEndpoint.Host is 'xyz' and ControlPlaneEndpoint.Port is 21. Value of ControlPlaneEndpoint.Host and ControlPlaneEndpoint.Port should remain unchanged", func(t *testing.T) {
hetznerCluster := &infrav1.HetznerCluster{
Spec: infrav1.HetznerClusterSpec{
ControlPlaneLoadBalancer: infrav1.LoadBalancerSpec{
UseIPv6Endpoint: true,
Enabled: true,
Port: 11,
},
ControlPlaneEndpoint: &clusterv1.APIEndpoint{
Host: "xyz",
Port: 21,
},
},
Status: infrav1.HetznerClusterStatus{
ControlPlaneLoadBalancer: &infrav1.LoadBalancerStatus{
IPv6: "abc",
},
},
}

processControlPlaneEndpoint(hetznerCluster)

if hetznerCluster.Spec.ControlPlaneEndpoint.Host != "xyz" {
t.Fatalf("Wrong value for Host set. Got: %s, Want: 'xyz'", hetznerCluster.Spec.ControlPlaneEndpoint.Host)
}

if hetznerCluster.Spec.ControlPlaneEndpoint.Port != 21 {
t.Fatalf("Wrong value for Port set. Got: %d, Want: 21", hetznerCluster.Spec.ControlPlaneEndpoint.Port)
}

if hetznerCluster.Status.Ready != true {
t.Fatalf("return value should be true")
}
})
}
5 changes: 3 additions & 2 deletions hack/kind-dev.sh
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,9 @@ kindV1Alpha4Cluster:
- role: control-plane
image: kindest/node:${CLUSTER_VERSION}
networking:
podSubnet: "10.244.0.0/16"
serviceSubnet: "10.96.0.0/12"
podSubnet: "10.244.0.0/16,fd00:10:244::/56"
serviceSubnet: "10.96.0.0/12,fd00:10:96::/112"
ipFamily: dual
EOF
}

Expand Down

0 comments on commit 375fdf0

Please sign in to comment.