Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

✨ Load Balancer IPv6 as apiserver endpoint #1227

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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" {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please use require.Equal(t, a, b)

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