Repository: hetznercloud/hcloud-cloud-controller-manager
Pull request #72 (2020-08-06 12:42:49)

Implement change load balancer type

fhofherr: > Keep in mind to add a changelog entry :)

Done

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 950292f3..44279cbf 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -9,6 +9,7 @@ master
 * Add `HCLOUD_LOAD_BALANCERS_ENABLED` env variable to disable the Load
   Balancer feature, per default the Load Balancers are enabled.
 * Use defaults if health check annotations are not set.
+* Add support for changing the Load Balancer type
 
 v1.6.1
 ------
diff --git a/go.mod b/go.mod
index 98a6fbc4..eb58df95 100644
--- a/go.mod
+++ b/go.mod
@@ -6,7 +6,7 @@ require (
 	github.com/NYTimes/gziphandler v1.1.1 // indirect
 	github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f // indirect
 	github.com/golang/groupcache v0.0.0-20171101203131-84a468cf14b4 // indirect
-	github.com/hetznercloud/hcloud-go v1.18.1
+	github.com/hetznercloud/hcloud-go v1.19.0
 	github.com/stretchr/testify v1.6.1
 	github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5 // indirect
 	gopkg.in/square/go-jose.v2 v2.3.0 // indirect
diff --git a/go.sum b/go.sum
index 2f695b42..7426ac3c 100644
--- a/go.sum
+++ b/go.sum
@@ -298,8 +298,8 @@ github.com/hashicorp/hcl v0.0.0-20180404174102-ef8a98b0bbce/go.mod h1:oZtUIOe8dh
 github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
 github.com/heketi/heketi v9.0.1-0.20190917153846-c2e2a4ab7ab9+incompatible/go.mod h1:bB9ly3RchcQqsQ9CpyaQwvva7RS5ytVoSoholZQON6o=
 github.com/heketi/tests v0.0.0-20151005000721-f3775cbcefd6/go.mod h1:xGMAM8JLi7UkZt1i4FQeQy0R2T8GLUwQhOP5M1gBhy4=
-github.com/hetznercloud/hcloud-go v1.18.1 h1:iI0/OrGcg2Cl2f4K7UFInWezcn5YuS+UVdfOI+mIMV4=
-github.com/hetznercloud/hcloud-go v1.18.1/go.mod h1:EhElojlVU1biA5JgBaV8rRU1vE5+iYke402kXC9pooE=
+github.com/hetznercloud/hcloud-go v1.19.0 h1:8g28MQg8Eg97K7GASKUnaTZSNKo9CB73Xfxbt/NjvnU=
+github.com/hetznercloud/hcloud-go v1.19.0/go.mod h1:EhElojlVU1biA5JgBaV8rRU1vE5+iYke402kXC9pooE=
 github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
 github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
 github.com/imdario/mergo v0.3.5 h1:JboBksRwiiAJWvIYJVo46AfV+IAIKZpfrSzVKj42R4Q=
diff --git a/internal/hcops/load_balancer.go b/internal/hcops/load_balancer.go
index 9b897310..10ae85f8 100644
--- a/internal/hcops/load_balancer.go
+++ b/internal/hcops/load_balancer.go
@@ -35,6 +35,7 @@ type HCloudLoadBalancerClient interface {
 	) (*hcloud.Action, *hcloud.Response, error)
 
 	ChangeAlgorithm(ctx context.Context, lb *hcloud.LoadBalancer, opts hcloud.LoadBalancerChangeAlgorithmOpts) (*hcloud.Action, *hcloud.Response, error)
+	ChangeType(ctx context.Context, lb *hcloud.LoadBalancer, opts hcloud.LoadBalancerChangeTypeOpts) (*hcloud.Action, *hcloud.Response, error)
 
 	AddServerTarget(ctx context.Context, lb *hcloud.LoadBalancer, opts hcloud.LoadBalancerAddServerTargetOpts) (*hcloud.Action, *hcloud.Response, error)
 	RemoveServerTarget(ctx context.Context, lb *hcloud.LoadBalancer, server *hcloud.Server) (*hcloud.Action, *hcloud.Response, error)
@@ -173,6 +174,12 @@ func (l *LoadBalancerOps) ReconcileHCLB(ctx context.Context, lb *hcloud.LoadBala
 	}
 	changed = changed || algorithmChanged
 
+	typeChanged, err := l.changeType(ctx, lb, svc)
+	if err != nil {
+		return changed, fmt.Errorf("%s: %w", op, err)
+	}
+	changed = changed || typeChanged
+
 	networkDetached, err := l.detachFromNetwork(ctx, lb)
 	if err != nil {
 		return changed, fmt.Errorf("%s: %w", op, err)
@@ -214,6 +221,29 @@ func (l *LoadBalancerOps) changeAlgorithm(ctx context.Context, lb *hcloud.LoadBa
 	return true, nil
 }
 
+func (l *LoadBalancerOps) changeType(ctx context.Context, lb *hcloud.LoadBalancer, svc *v1.Service) (bool, error) {
+	const op = "hcops/LoadBalancerOps.changeType"
+
+	lt, ok := annotation.LBType.StringFromService(svc)
+	if !ok {
+		return false, nil
+	}
+	if lt == lb.LoadBalancerType.Name {
+		return false, nil
+	}
+
+	opts := hcloud.LoadBalancerChangeTypeOpts{LoadBalancerType: &hcloud.LoadBalancerType{Name: lt}}
+	action, _, err := l.LBClient.ChangeType(ctx, lb, opts)
+	if err != nil {
+		return false, fmt.Errorf("%s: %w", op, err)
+	}
+	err = WatchAction(ctx, l.ActionClient, action)
+	if err != nil {
+		return false, fmt.Errorf("%s: %w", op, err)
+	}
+	return true, nil
+}
+
 func (l *LoadBalancerOps) detachFromNetwork(ctx context.Context, lb *hcloud.LoadBalancer) (bool, error) {
 	const op = "hcops/LoadBalancerOps.detachFromNetwork"
 	var changed bool
diff --git a/internal/mocks/loadbalancer.go b/internal/mocks/loadbalancer.go
index 7baa17e9..7ff2b6f2 100644
--- a/internal/mocks/loadbalancer.go
+++ b/internal/mocks/loadbalancer.go
@@ -56,6 +56,13 @@ func (m *LoadBalancerClient) ChangeAlgorithm(
 	return getActionPtr(args, 0), getResponsePtr(args, 1), args.Error(2)
 }
 
+func (m *LoadBalancerClient) ChangeType(
+	ctx context.Context, lb *hcloud.LoadBalancer, opts hcloud.LoadBalancerChangeTypeOpts,
+) (*hcloud.Action, *hcloud.Response, error) {
+	args := m.Called(ctx, lb, opts)
+	return getActionPtr(args, 0), getResponsePtr(args, 1), args.Error(2)
+}
+
 func (m *LoadBalancerClient) AddServerTarget(
 	ctx context.Context, lb *hcloud.LoadBalancer, opts hcloud.LoadBalancerAddServerTargetOpts,
 ) (*hcloud.Action, *hcloud.Response, error) {


Test patch:
diff --git a/internal/hcops/load_balancer_test.go b/internal/hcops/load_balancer_test.go
index c41afe93..6351bdd7 100644
--- a/internal/hcops/load_balancer_test.go
+++ b/internal/hcops/load_balancer_test.go
@@ -455,6 +455,51 @@ func TestLoadBalancerOps_ReconcileHCLB(t *testing.T) {
 				assert.False(t, changed)
 			},
 		},
+		{
+			name: "update type",
+			serviceAnnotations: map[annotation.Name]interface{}{
+				annotation.LBType: "lb21",
+			},
+			initialLB: &hcloud.LoadBalancer{
+				ID: 1,
+				LoadBalancerType: &hcloud.LoadBalancerType{
+					Name: "lb11",
+				},
+			},
+			mock: func(t *testing.T, tt *LBReconcilementTestCase) {
+				opts := hcloud.LoadBalancerChangeTypeOpts{
+					LoadBalancerType: &hcloud.LoadBalancerType{Name: "lb21"},
+				}
+
+				action := &hcloud.Action{ID: 4711}
+				tt.fx.LBClient.
+					On("ChangeType", tt.fx.Ctx, tt.initialLB, opts).
+					Return(action, nil, nil)
+				tt.fx.MockWatchProgress(action, nil)
+			},
+			perform: func(t *testing.T, tt *LBReconcilementTestCase) {
+				changed, err := tt.fx.LBOps.ReconcileHCLB(tt.fx.Ctx, tt.initialLB, tt.service)
+				assert.NoError(t, err)
+				assert.True(t, changed)
+			},
+		},
+		{
+			name: "don't update unchanged type",
+			serviceAnnotations: map[annotation.Name]interface{}{
+				annotation.LBType: "lb21",
+			},
+			initialLB: &hcloud.LoadBalancer{
+				ID: 1,
+				LoadBalancerType: &hcloud.LoadBalancerType{
+					Name: "lb21",
+				},
+			},
+			perform: func(t *testing.T, tt *LBReconcilementTestCase) {
+				changed, err := tt.fx.LBOps.ReconcileHCLB(tt.fx.Ctx, tt.initialLB, tt.service)
+				assert.NoError(t, err)
+				assert.False(t, changed)
+			},
+		},
 		{
 			name: "detach Load Balancer from network",
 			initialLB: &hcloud.LoadBalancer{