From 00affbf5c73af9ca37642033ec3f3c2cd75139de Mon Sep 17 00:00:00 2001 From: Ferdinand Hofherr Date: Wed, 5 May 2021 15:05:31 +0200 Subject: [PATCH] Add option to disable IPv6 for load balancers Enabling IPv6 by default in #137 broke clusters using external-dns. This commit adds an option to disable IPv6 by setting the load-balancer.hetzner.cloud/ipv6-disabled annotation to false. Closes #191 --- hcloud/load_balancers.go | 35 ++++++++++----- hcloud/load_balancers_test.go | 66 ++++++++++++++++++++++++++-- internal/annotation/load_balancer.go | 7 +++ 3 files changed, 93 insertions(+), 15 deletions(-) diff --git a/hcloud/load_balancers.go b/hcloud/load_balancers.go index 3a96c0d61..544c3b433 100644 --- a/hcloud/load_balancers.go +++ b/hcloud/load_balancers.go @@ -59,14 +59,23 @@ func (l *loadBalancers) GetLoadBalancer( }, true, nil } - return &v1.LoadBalancerStatus{Ingress: []v1.LoadBalancerIngress{ + ingresses := []v1.LoadBalancerIngress{ { IP: lb.PublicNet.IPv4.IP.String(), }, - { + } + + disableIPV6, err := annotation.LBIPv6Disabled.BoolFromService(service) + if err != nil && !errors.Is(err, annotation.ErrNotSet) { + return nil, false, fmt.Errorf("%s: %v", op, err) + } + if !disableIPV6 { + ingresses = append(ingresses, v1.LoadBalancerIngress{ IP: lb.PublicNet.IPv6.IP.String(), - }, - }}, true, nil + }) + } + + return &v1.LoadBalancerStatus{Ingress: ingresses}, true, nil } func (l *loadBalancers) GetLoadBalancerName(ctx context.Context, clusterName string, service *v1.Service) string { @@ -165,15 +174,17 @@ func (l *loadBalancers) EnsureLoadBalancer( if err != nil && !errors.Is(err, annotation.ErrNotSet) { return nil, fmt.Errorf("%s: %w", op, err) } + if !disablePubNet { - ingress = append(ingress, - v1.LoadBalancerIngress{ - IP: lb.PublicNet.IPv4.IP.String(), - }, - v1.LoadBalancerIngress{ - IP: lb.PublicNet.IPv6.IP.String(), - }, - ) + ingress = append(ingress, v1.LoadBalancerIngress{IP: lb.PublicNet.IPv4.IP.String()}) + + disableIPV6, err := annotation.LBIPv6Disabled.BoolFromService(svc) + if err != nil && !errors.Is(err, annotation.ErrNotSet) { + return nil, fmt.Errorf("%s: %v", op, err) + } + if !disableIPV6 { + ingress = append(ingress, v1.LoadBalancerIngress{IP: lb.PublicNet.IPv6.IP.String()}) + } } disablePrivIngress, err := l.getDisablePrivateIngress(svc) diff --git a/hcloud/load_balancers_test.go b/hcloud/load_balancers_test.go index 886124304..e7a4345d0 100644 --- a/hcloud/load_balancers_test.go +++ b/hcloud/load_balancers_test.go @@ -14,6 +14,35 @@ import ( func TestLoadBalancers_GetLoadBalancer(t *testing.T) { tests := []LoadBalancerTestCase{ + { + Name: "get load balancer without host name IPv6 disabled", + ServiceUID: "1", + ServiceAnnotations: map[annotation.Name]interface{}{ + annotation.LBIPv6Disabled: true, + }, + LB: &hcloud.LoadBalancer{ + ID: 1, + Name: "no-host-name", + PublicNet: hcloud.LoadBalancerPublicNet{ + IPv4: hcloud.LoadBalancerPublicNetIPv4{IP: net.ParseIP("1.2.3.4")}, + }, + }, + Mock: func(t *testing.T, tt *LoadBalancerTestCase) { + tt.LBOps. + On("GetByK8SServiceUID", tt.Ctx, tt.Service). + Return(tt.LB, nil) + }, + Perform: func(t *testing.T, tt *LoadBalancerTestCase) { + status, exists, err := tt.LoadBalancers.GetLoadBalancer(tt.Ctx, tt.ClusterName, tt.Service) + assert.NoError(t, err) + assert.True(t, exists) + + if !assert.Len(t, status.Ingress, 1) { + return + } + assert.Equal(t, tt.LB.PublicNet.IPv4.IP.String(), status.Ingress[0].IP) + }, + }, { Name: "get load balancer without host name", ServiceUID: "1", @@ -132,6 +161,37 @@ func TestLoadBalancers_EnsureLoadBalancer_CreateLoadBalancer(t *testing.T) { assert.EqualError(t, err, "hcloud/loadBalancers.EnsureLoadBalancer: test error") }, }, + { + Name: "public network only no ipv6", + ServiceUID: "2", + ServiceAnnotations: map[annotation.Name]interface{}{ + annotation.LBName: "pub-net-only-no-ipv6", + annotation.LBIPv6Disabled: true, + }, + LB: &hcloud.LoadBalancer{ + ID: 1, + Name: "pub-net-only-no-ipv6", + LoadBalancerType: &hcloud.LoadBalancerType{Name: "lb11"}, + Location: &hcloud.Location{Name: "nbg1", NetworkZone: hcloud.NetworkZoneEUCentral}, + PublicNet: hcloud.LoadBalancerPublicNet{ + Enabled: true, + IPv4: hcloud.LoadBalancerPublicNetIPv4{IP: net.ParseIP("1.2.3.4")}, + }, + }, + Mock: func(t *testing.T, tt *LoadBalancerTestCase) { + setupSuccessMocks(tt, "pub-net-only-no-ipv6") + }, + Perform: func(t *testing.T, tt *LoadBalancerTestCase) { + expected := &v1.LoadBalancerStatus{ + Ingress: []v1.LoadBalancerIngress{ + {IP: tt.LB.PublicNet.IPv4.IP.String()}, + }, + } + lbStat, err := tt.LoadBalancers.EnsureLoadBalancer(tt.Ctx, tt.ClusterName, tt.Service, tt.Nodes) + assert.NoError(t, err) + assert.Equal(t, expected, lbStat) + }, + }, { Name: "public network only", ServiceUID: "2", @@ -223,7 +283,7 @@ func TestLoadBalancers_EnsureLoadBalancer_CreateLoadBalancer(t *testing.T) { PublicNet: hcloud.LoadBalancerPublicNet{ Enabled: true, IPv4: hcloud.LoadBalancerPublicNetIPv4{IP: net.ParseIP("1.2.3.4")}, - // IPv6: hcloud.LoadBalancerPublicNetIPv6{IP: net.ParseIP("fe80::1")}, + IPv6: hcloud.LoadBalancerPublicNetIPv6{IP: net.ParseIP("fe80::1")}, }, PrivateNet: []hcloud.LoadBalancerPrivateNet{ { @@ -256,7 +316,7 @@ func TestLoadBalancers_EnsureLoadBalancer_CreateLoadBalancer(t *testing.T) { ServiceUID: "5", ServiceAnnotations: map[annotation.Name]interface{}{ annotation.LBName: "with-priv-net-no-priv-ingress", - annotation.LBDisablePrivateIngress: "true", + annotation.LBDisablePrivateIngress: true, }, LB: &hcloud.LoadBalancer{ ID: 1, @@ -266,7 +326,7 @@ func TestLoadBalancers_EnsureLoadBalancer_CreateLoadBalancer(t *testing.T) { PublicNet: hcloud.LoadBalancerPublicNet{ Enabled: true, IPv4: hcloud.LoadBalancerPublicNetIPv4{IP: net.ParseIP("1.2.3.4")}, - // IPv6: hcloud.LoadBalancerPublicNetIPv6{IP: net.ParseIP("fe80::1")}, + IPv6: hcloud.LoadBalancerPublicNetIPv6{IP: net.ParseIP("fe80::1")}, }, PrivateNet: []hcloud.LoadBalancerPrivateNet{ { diff --git a/internal/annotation/load_balancer.go b/internal/annotation/load_balancer.go index 83cae7435..28cdb9a41 100644 --- a/internal/annotation/load_balancer.go +++ b/internal/annotation/load_balancer.go @@ -20,6 +20,13 @@ const ( // the backend. Read-only. LBPublicIPv6 Name = "load-balancer.hetzner.cloud/ipv6" + // LBIPv6Disabled disables the use of IPv6 for the Load Balancer. + // + // Set this annotation if you use external-dns. + // + // Default: false + LBIPv6Disabled Name = "load-balancer.hetzner.cloud/ipv6-disabled" + // LBName is the name of the Load Balancer. The name will be visible in // the Hetzner Cloud API console. LBName Name = "load-balancer.hetzner.cloud/name"