From bfa00a352b1e9ab23ce6903af02c162a6e0d53c2 Mon Sep 17 00:00:00 2001 From: Cedric Kienzler Date: Wed, 13 Sep 2023 15:33:39 +0200 Subject: [PATCH 1/2] Add HealthStatus to loadbalancer list output This commit adds the health status of a loadbalancer to the list command to display the loadbalancer health in a similar style as in the browser --- internal/cmd/loadbalancer/list.go | 75 ++++++++++++++++++++++++++++++- 1 file changed, 74 insertions(+), 1 deletion(-) diff --git a/internal/cmd/loadbalancer/list.go b/internal/cmd/loadbalancer/list.go index 9edf9e49..b49bfe55 100644 --- a/internal/cmd/loadbalancer/list.go +++ b/internal/cmd/loadbalancer/list.go @@ -18,7 +18,7 @@ import ( var ListCmd = base.ListCmd{ ResourceNamePlural: "Load Balancer", - DefaultColumns: []string{"id", "name", "ipv4", "ipv6", "type", "location", "network_zone", "age"}, + DefaultColumns: []string{"id", "name", "health", "ipv4", "ipv6", "type", "location", "network_zone", "age"}, Fetch: func(ctx context.Context, client hcapi2.Client, cmd *cobra.Command, listOpts hcloud.ListOpts, sorts []string) ([]interface{}, error) { opts := hcloud.LoadBalancerListOpts{ListOpts: listOpts} if len(sorts) > 0 { @@ -75,6 +75,10 @@ var ListCmd = base.ListCmd{ AddFieldFn("age", output.FieldFn(func(obj interface{}) string { loadBalancer := obj.(*hcloud.LoadBalancer) return util.Age(loadBalancer.Created, time.Now()) + })). + AddFieldFn("health", output.FieldFn(func(obj interface{}) string { + loadBalancer := obj.(*hcloud.LoadBalancer) + return loadBalancerHealth(loadBalancer) })) }, @@ -167,3 +171,72 @@ var ListCmd = base.ListCmd{ return loadBalancerSchemas }, } + +func loadBalancerHealth(l *hcloud.LoadBalancer) string { + healthyCount := 0 + unhealthyCount := 0 + unknownCount := 0 + + for _, lbTarget := range l.Targets { + switch loadBalancerTargetHealth(&lbTarget) { + case string(hcloud.LoadBalancerTargetHealthStatusStatusHealthy): + healthyCount++ + + case string(hcloud.LoadBalancerTargetHealthStatusStatusUnhealthy): + unhealthyCount++ + + case "mixed": + return "mixed" + + default: + unknownCount++ + } + } + + switch len(l.Targets) { + case healthyCount: + return string(hcloud.LoadBalancerTargetHealthStatusStatusHealthy) + + case unhealthyCount: + return string(hcloud.LoadBalancerTargetHealthStatusStatusUnhealthy) + + case unknownCount: + return string(hcloud.LoadBalancerTargetHealthStatusStatusUnknown) + + default: + return "mixed" + } +} + +func loadBalancerTargetHealth(t *hcloud.LoadBalancerTarget) string { + healthyCount := 0 + unhealthyCount := 0 + unknownCount := 0 + + for _, targetHealth := range t.HealthStatus { + switch targetHealth.Status { + case hcloud.LoadBalancerTargetHealthStatusStatusHealthy: + healthyCount++ + + case hcloud.LoadBalancerTargetHealthStatusStatusUnhealthy: + unhealthyCount++ + + default: + unknownCount++ + } + } + + switch len(t.HealthStatus) { + case healthyCount: + return string(hcloud.LoadBalancerTargetHealthStatusStatusHealthy) + + case unhealthyCount: + return string(hcloud.LoadBalancerTargetHealthStatusStatusUnhealthy) + + case unknownCount: + return string(hcloud.LoadBalancerTargetHealthStatusStatusUnknown) + + default: + return "mixed" + } +} From 55b0b7e7c3711bfd6f32659324c38c4819f4f25a Mon Sep 17 00:00:00 2001 From: Cedric Kienzler Date: Tue, 19 Sep 2023 13:20:58 +0200 Subject: [PATCH 2/2] Pullrequest comments --- internal/cmd/loadbalancer/list.go | 54 ++----- internal/cmd/loadbalancer/list_test.go | 202 +++++++++++++++++++++++++ 2 files changed, 212 insertions(+), 44 deletions(-) create mode 100644 internal/cmd/loadbalancer/list_test.go diff --git a/internal/cmd/loadbalancer/list.go b/internal/cmd/loadbalancer/list.go index b49bfe55..47b9a371 100644 --- a/internal/cmd/loadbalancer/list.go +++ b/internal/cmd/loadbalancer/list.go @@ -178,55 +178,21 @@ func loadBalancerHealth(l *hcloud.LoadBalancer) string { unknownCount := 0 for _, lbTarget := range l.Targets { - switch loadBalancerTargetHealth(&lbTarget) { - case string(hcloud.LoadBalancerTargetHealthStatusStatusHealthy): - healthyCount++ + for _, svcHealth := range lbTarget.HealthStatus { + switch svcHealth.Status { + case hcloud.LoadBalancerTargetHealthStatusStatusHealthy: + healthyCount++ - case string(hcloud.LoadBalancerTargetHealthStatusStatusUnhealthy): - unhealthyCount++ + case hcloud.LoadBalancerTargetHealthStatusStatusUnhealthy: + unhealthyCount++ - case "mixed": - return "mixed" - - default: - unknownCount++ - } - } - - switch len(l.Targets) { - case healthyCount: - return string(hcloud.LoadBalancerTargetHealthStatusStatusHealthy) - - case unhealthyCount: - return string(hcloud.LoadBalancerTargetHealthStatusStatusUnhealthy) - - case unknownCount: - return string(hcloud.LoadBalancerTargetHealthStatusStatusUnknown) - - default: - return "mixed" - } -} - -func loadBalancerTargetHealth(t *hcloud.LoadBalancerTarget) string { - healthyCount := 0 - unhealthyCount := 0 - unknownCount := 0 - - for _, targetHealth := range t.HealthStatus { - switch targetHealth.Status { - case hcloud.LoadBalancerTargetHealthStatusStatusHealthy: - healthyCount++ - - case hcloud.LoadBalancerTargetHealthStatusStatusUnhealthy: - unhealthyCount++ - - default: - unknownCount++ + default: + unknownCount++ + } } } - switch len(t.HealthStatus) { + switch len(l.Targets) * len(l.Services) { case healthyCount: return string(hcloud.LoadBalancerTargetHealthStatusStatusHealthy) diff --git a/internal/cmd/loadbalancer/list_test.go b/internal/cmd/loadbalancer/list_test.go new file mode 100644 index 00000000..54a14931 --- /dev/null +++ b/internal/cmd/loadbalancer/list_test.go @@ -0,0 +1,202 @@ +package loadbalancer + +import ( + "testing" + + "github.com/hetznercloud/hcloud-go/v2/hcloud" + "github.com/stretchr/testify/assert" +) + +func TestLoadBalancerHealth(t *testing.T) { + tests := []struct { + name string + lb *hcloud.LoadBalancer + expected string + }{ + { + name: "healthy", + lb: &hcloud.LoadBalancer{ + Name: "foobar", + Services: make([]hcloud.LoadBalancerService, 1), + Targets: []hcloud.LoadBalancerTarget{ + { + HealthStatus: []hcloud.LoadBalancerTargetHealthStatus{ + { + Status: hcloud.LoadBalancerTargetHealthStatusStatusHealthy, + }, + }, + }, + }, + }, + expected: string(hcloud.LoadBalancerTargetHealthStatusStatusHealthy), + }, + { + name: "unhealthy", + lb: &hcloud.LoadBalancer{ + Name: "foobar", + Services: make([]hcloud.LoadBalancerService, 1), + Targets: []hcloud.LoadBalancerTarget{ + { + HealthStatus: []hcloud.LoadBalancerTargetHealthStatus{ + { + Status: hcloud.LoadBalancerTargetHealthStatusStatusUnhealthy, + }, + }, + }, + }, + }, + expected: string(hcloud.LoadBalancerTargetHealthStatusStatusUnhealthy), + }, + { + name: "unknown", + lb: &hcloud.LoadBalancer{ + Name: "foobar", + Services: make([]hcloud.LoadBalancerService, 1), + Targets: []hcloud.LoadBalancerTarget{ + { + HealthStatus: []hcloud.LoadBalancerTargetHealthStatus{ + { + Status: hcloud.LoadBalancerTargetHealthStatusStatusUnknown, + }, + }, + }, + }, + }, + expected: string(hcloud.LoadBalancerTargetHealthStatusStatusUnknown), + }, + { + name: "mixed", + lb: &hcloud.LoadBalancer{ + Name: "foobar", + Services: make([]hcloud.LoadBalancerService, 1), + Targets: []hcloud.LoadBalancerTarget{ + { + HealthStatus: []hcloud.LoadBalancerTargetHealthStatus{ + { + Status: hcloud.LoadBalancerTargetHealthStatusStatusHealthy, + }, + }, + }, + { + HealthStatus: []hcloud.LoadBalancerTargetHealthStatus{ + { + Status: hcloud.LoadBalancerTargetHealthStatusStatusUnhealthy, + }, + }, + }, + { + HealthStatus: []hcloud.LoadBalancerTargetHealthStatus{ + { + Status: hcloud.LoadBalancerTargetHealthStatusStatusUnknown, + }, + }, + }, + }, + }, + expected: "mixed", + }, + { + name: "mixed_many_services_grouped_by_target", + lb: &hcloud.LoadBalancer{ + Name: "foobar", + Services: make([]hcloud.LoadBalancerService, 3), + Targets: []hcloud.LoadBalancerTarget{ + { + HealthStatus: []hcloud.LoadBalancerTargetHealthStatus{ + { + Status: hcloud.LoadBalancerTargetHealthStatusStatusHealthy, + }, + { + Status: hcloud.LoadBalancerTargetHealthStatusStatusHealthy, + }, + { + Status: hcloud.LoadBalancerTargetHealthStatusStatusHealthy, + }, + }, + }, + { + HealthStatus: []hcloud.LoadBalancerTargetHealthStatus{ + { + Status: hcloud.LoadBalancerTargetHealthStatusStatusUnhealthy, + }, + { + Status: hcloud.LoadBalancerTargetHealthStatusStatusUnhealthy, + }, + { + Status: hcloud.LoadBalancerTargetHealthStatusStatusUnhealthy, + }, + }, + }, + { + HealthStatus: []hcloud.LoadBalancerTargetHealthStatus{ + { + Status: hcloud.LoadBalancerTargetHealthStatusStatusUnknown, + }, + { + Status: hcloud.LoadBalancerTargetHealthStatusStatusUnknown, + }, + { + Status: hcloud.LoadBalancerTargetHealthStatusStatusUnknown, + }, + }, + }, + }, + }, + expected: "mixed", + }, + { + name: "mixed_many_services_mixed", + lb: &hcloud.LoadBalancer{ + Name: "foobar", + Services: make([]hcloud.LoadBalancerService, 3), + Targets: []hcloud.LoadBalancerTarget{ + { + HealthStatus: []hcloud.LoadBalancerTargetHealthStatus{ + { + Status: hcloud.LoadBalancerTargetHealthStatusStatusHealthy, + }, + { + Status: hcloud.LoadBalancerTargetHealthStatusStatusUnhealthy, + }, + { + Status: hcloud.LoadBalancerTargetHealthStatusStatusUnknown, + }, + }, + }, + { + HealthStatus: []hcloud.LoadBalancerTargetHealthStatus{ + { + Status: hcloud.LoadBalancerTargetHealthStatusStatusHealthy, + }, + { + Status: hcloud.LoadBalancerTargetHealthStatusStatusUnhealthy, + }, + { + Status: hcloud.LoadBalancerTargetHealthStatusStatusUnknown, + }, + }, + }, + { + HealthStatus: []hcloud.LoadBalancerTargetHealthStatus{ + { + Status: hcloud.LoadBalancerTargetHealthStatusStatusHealthy, + }, + { + Status: hcloud.LoadBalancerTargetHealthStatusStatusUnhealthy, + }, + { + Status: hcloud.LoadBalancerTargetHealthStatusStatusUnknown, + }, + }, + }, + }, + }, + expected: "mixed", + }, + } + + for _, test := range tests { + res := loadBalancerHealth(test.lb) + assert.Equal(t, test.expected, res, test.name) + } +}