From 58042b79b03266c68b062b59442140693e24e24f Mon Sep 17 00:00:00 2001 From: Ezequiel Lopes Date: Tue, 5 Nov 2024 15:04:46 -0300 Subject: [PATCH] Feat/auto scale behavior client (#235) * Update Go version to 1.22 and toolchain to go1.23.2 * feat: Add test for AutoScaleBehaviorSet function * feat: Add flags for autoscale down behavior settings * fix: corrected package versions * feat: Add scale down behavior details to app string representation * feat: Implement scale down behavior JSON handling in client * test: checking auto scale down output in app details --- go.mod | 2 +- go.sum | 4 +- tsuru/client/apps.go | 9 ++- tsuru/client/apps_test.go | 138 ++++++++++++++++++++++----------- tsuru/client/apps_type.go | 51 ++++++++++++ tsuru/client/autoscale.go | 16 ++-- tsuru/client/autoscale_test.go | 71 +++++++++++++++++ 7 files changed, 233 insertions(+), 58 deletions(-) create mode 100644 tsuru/client/apps_type.go diff --git a/go.mod b/go.mod index b717cf34..3bf0c00a 100644 --- a/go.mod +++ b/go.mod @@ -15,7 +15,7 @@ require ( github.com/pmorie/go-open-service-broker-client v0.0.0-20180330214919-dca737037ce6 github.com/sabhiram/go-gitignore v0.0.0-20171017070213-362f9845770f github.com/tsuru/gnuflag v0.0.0-20151217162021-86b8c1b864aa - github.com/tsuru/go-tsuruclient v0.0.0-20240812213541-df5e446efabf + github.com/tsuru/go-tsuruclient v0.0.0-20241029183502-e219a905d873 github.com/tsuru/tablecli v0.0.0-20190131152944-7ded8a3383c6 github.com/tsuru/tsuru v0.0.0-20240703132558-bfd1d9c89602 golang.org/x/net v0.25.0 diff --git a/go.sum b/go.sum index 59bb9882..1324faf2 100644 --- a/go.sum +++ b/go.sum @@ -155,8 +155,8 @@ github.com/tsuru/config v0.0.0-20201023175036-375aaee8b560 h1:fniQ/BmYAHdnNmY333 github.com/tsuru/config v0.0.0-20201023175036-375aaee8b560/go.mod h1:mj6t8JKWU51GScTT50XRmDj65T5XhTyNvO5FUNV5zS4= github.com/tsuru/gnuflag v0.0.0-20151217162021-86b8c1b864aa h1:JlLQP1xa13a994p/Aau2e3K9xXYaHNoNvTDVIMHSUa4= github.com/tsuru/gnuflag v0.0.0-20151217162021-86b8c1b864aa/go.mod h1:UibOSvkMFKRe/eiwktAPAvQG8L+p8nYsECJvu3Dgw7I= -github.com/tsuru/go-tsuruclient v0.0.0-20240812213541-df5e446efabf h1:Jwxn42PtNz2bbOYKwQWFUMlo3sYy2LIYEDFcEPej7M0= -github.com/tsuru/go-tsuruclient v0.0.0-20240812213541-df5e446efabf/go.mod h1:qwh/KJ6ypa2GISRI79XFOHhnSjGOe1cZVPHF3nfrf18= +github.com/tsuru/go-tsuruclient v0.0.0-20241029183502-e219a905d873 h1:Rs3urDCvqLpmGpUKOJNRiOCij/A+EcemdeOaGmGcs/E= +github.com/tsuru/go-tsuruclient v0.0.0-20241029183502-e219a905d873/go.mod h1:qwh/KJ6ypa2GISRI79XFOHhnSjGOe1cZVPHF3nfrf18= github.com/tsuru/tablecli v0.0.0-20190131152944-7ded8a3383c6 h1:1XDdWFAjIbCSG1OjN9v9KdWhuM8UtYlFcfHe/Ldkchk= github.com/tsuru/tablecli v0.0.0-20190131152944-7ded8a3383c6/go.mod h1:ztYpOhW+u1k21FEqp7nZNgpWbr0dUKok5lgGCZi+1AQ= github.com/tsuru/tsuru v0.0.0-20240703132558-bfd1d9c89602 h1:HiF99OFCkd2F0DwyMzBDStxm43rtrK8sBnVA2ZyfIZ4= diff --git a/tsuru/client/apps.go b/tsuru/client/apps.go index b52b29c4..89ab8e5e 100644 --- a/tsuru/client/apps.go +++ b/tsuru/client/apps.go @@ -792,7 +792,14 @@ func (a *app) String(simplified bool) string { prometheusInfo, })) } - + scaleDown := getParamsScaleDownJson(as.Behavior) + autoScaleTable.AddRow([]string{ + "Scale Down Behavior", + fmt.Sprintf("Units: %s\nPercentage: %s%%\nStabilization Window: %ss", + scaleDown.UnitsPolicyValue, + scaleDown.PercentagePolicyValue, + scaleDown.StabilizationWindow), + }) autoScaleTables = append(autoScaleTables, autoScaleTable) } diff --git a/tsuru/client/apps_test.go b/tsuru/client/apps_test.go index e234674e..951d0b0d 100644 --- a/tsuru/client/apps_test.go +++ b/tsuru/client/apps_test.go @@ -1570,14 +1570,28 @@ func (s *S) TestAppInfoWithAutoScale(c *check.C) { "minUnits":1, "maxUnits":10, "averageCPU":"500m", - "version":10 + "version":10, + "behavior": { + "scaleDown": { + "percentagePolicyValue": 30, + "unitsPolicyValue": 20, + "stabilizationWindow": 100 + } + } }, { "process":"worker", "minUnits":2, "maxUnits":5, "averageCPU":"2", - "version":10 + "version":10, + "behavior": { + "scaleDown": { + "percentagePolicyValue": 10, + "unitsPolicyValue": 10, + "stabilizationWindow": 60 + } + } } ] }` @@ -1608,18 +1622,26 @@ Units [process worker]: 1 Auto Scale: Process: web (v10), Min Units: 1, Max Units: 10 -+----------+-----------------+ -| Triggers | Trigger details | -+----------+-----------------+ -| CPU | Target: 50% | -+----------+-----------------+ ++---------------------+----------------------------+ +| Triggers | Trigger details | ++---------------------+----------------------------+ +| CPU | Target: 50% | ++---------------------+----------------------------+ +| Scale Down Behavior | Units: 20 | +| | Percentage: 30% | +| | Stabilization Window: 100s | ++---------------------+----------------------------+ Process: worker (v10), Min Units: 2, Max Units: 5 -+----------+-----------------+ -| Triggers | Trigger details | -+----------+-----------------+ -| CPU | Target: 200% | -+----------+-----------------+ ++---------------------+---------------------------+ +| Triggers | Trigger details | ++---------------------+---------------------------+ +| CPU | Target: 200% | ++---------------------+---------------------------+ +| Scale Down Behavior | Units: 10 | +| | Percentage: 10% | +| | Stabilization Window: 60s | ++---------------------+---------------------------+ ` context := cmd.Context{ @@ -1672,6 +1694,13 @@ func (s *S) TestAppInfoWithKEDAAutoScale(c *check.C) { "maxUnits":10, "averageCPU":"500m", "version":10, + "behavior": { + "scaleDown": { + "percentagePolicyValue": 21, + "unitsPolicyValue": 25, + "stabilizationWindow": 50 + } + }, "schedules": [ { "minReplicas":2, @@ -1705,6 +1734,13 @@ func (s *S) TestAppInfoWithKEDAAutoScale(c *check.C) { "maxUnits":5, "averageCPU":"2", "version":10, + "behavior": { + "scaleDown": { + "percentagePolicyValue": 5, + "unitsPolicyValue": 7, + "stabilizationWindow": 60 + } + }, "schedules": [ { "minReplicas":1, @@ -1743,43 +1779,51 @@ Units [process worker]: 1 Auto Scale: Process: web (v10), Min Units: 1, Max Units: 10 -+------------+-------------------------------------------+ -| Triggers | Trigger details | -+------------+-------------------------------------------+ -| CPU | Target: 50% | -+------------+-------------------------------------------+ -| Schedule | Start: At 06:00 AM (0 6 * * *) | -| | End: At 06:00 PM (0 18 * * *) | -| | Units: 2 | -| | Timezone: UTC | -+------------+-------------------------------------------+ -| Schedule | Start: At 12:00 PM (0 12 * * *) | -| | End: At 03:00 PM (0 15 * * *) | -| | Units: 3 | -| | Timezone: UTC | -+------------+-------------------------------------------+ -| Prometheus | Name: my_metric_id | -| | Query: my_query{app='my-app'} | -| | Threshold: 2 | -| | PrometheusAddress: default.com | -+------------+-------------------------------------------+ -| Prometheus | Name: my_metric_id_2 | -| | Query: my_query_2{app='my-app'} | -| | Threshold: 5 | -| | PrometheusAddress: exemple.prometheus.com | -+------------+-------------------------------------------+ ++---------------------+-------------------------------------------+ +| Triggers | Trigger details | ++---------------------+-------------------------------------------+ +| CPU | Target: 50% | ++---------------------+-------------------------------------------+ +| Schedule | Start: At 06:00 AM (0 6 * * *) | +| | End: At 06:00 PM (0 18 * * *) | +| | Units: 2 | +| | Timezone: UTC | ++---------------------+-------------------------------------------+ +| Schedule | Start: At 12:00 PM (0 12 * * *) | +| | End: At 03:00 PM (0 15 * * *) | +| | Units: 3 | +| | Timezone: UTC | ++---------------------+-------------------------------------------+ +| Prometheus | Name: my_metric_id | +| | Query: my_query{app='my-app'} | +| | Threshold: 2 | +| | PrometheusAddress: default.com | ++---------------------+-------------------------------------------+ +| Prometheus | Name: my_metric_id_2 | +| | Query: my_query_2{app='my-app'} | +| | Threshold: 5 | +| | PrometheusAddress: exemple.prometheus.com | ++---------------------+-------------------------------------------+ +| Scale Down Behavior | Units: 25 | +| | Percentage: 21% | +| | Stabilization Window: 50s | ++---------------------+-------------------------------------------+ Process: worker (v10), Min Units: 2, Max Units: 5 -+----------+--------------------------------+ -| Triggers | Trigger details | -+----------+--------------------------------+ -| CPU | Target: 200% | -+----------+--------------------------------+ -| Schedule | Start: At 12:00 AM (0 0 * * *) | -| | End: At 06:00 AM (0 6 * * *) | -| | Units: 1 | -| | Timezone: America/Sao_Paulo | -+----------+--------------------------------+ ++---------------------+--------------------------------+ +| Triggers | Trigger details | ++---------------------+--------------------------------+ +| CPU | Target: 200% | ++---------------------+--------------------------------+ +| Schedule | Start: At 12:00 AM (0 0 * * *) | +| | End: At 06:00 AM (0 6 * * *) | +| | Units: 1 | +| | Timezone: America/Sao_Paulo | ++---------------------+--------------------------------+ +| Scale Down Behavior | Units: 7 | +| | Percentage: 5% | +| | Stabilization Window: 60s | ++---------------------+--------------------------------+ ` context := cmd.Context{ diff --git a/tsuru/client/apps_type.go b/tsuru/client/apps_type.go new file mode 100644 index 00000000..e05cc0bb --- /dev/null +++ b/tsuru/client/apps_type.go @@ -0,0 +1,51 @@ +// Copyright 2016 tsuru-client authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package client + +import ( + "encoding/json" + "fmt" + + "github.com/tsuru/go-tsuruclient/pkg/tsuru" +) + +type behaviorScaleDownJson struct { + ScaleDown scaleDownJson `json:"scaleDown"` +} + +type scaleDownJson struct { + UnitsPolicyValue *int32 `json:"unitsPolicyValue,omitempty"` + PercentagePolicyValue *int32 `json:"percentagePolicyValue,omitempty"` + StabilizationWindow *int32 `json:"stabilizationWindow,omitempty"` +} + +type scaleDownOutput struct { + UnitsPolicyValue string `json:"unitsPolicyValue,omitempty"` + PercentagePolicyValue string `json:"percentagePolicyValue,omitempty"` + StabilizationWindow string `json:"stabilizationWindow,omitempty"` +} + +func getParamsScaleDownJson(behavior tsuru.AutoScaleSpecBehavior) scaleDownOutput { + b, err := json.Marshal(behavior) + if err != nil { + return scaleDownOutput{} + } + var behaviorJson behaviorScaleDownJson + err = json.Unmarshal(b, &behaviorJson) + if err != nil { + return scaleDownOutput{} + } + output := scaleDownOutput{} + if behaviorJson.ScaleDown.UnitsPolicyValue != nil { + output.UnitsPolicyValue = fmt.Sprintf("%d", *behaviorJson.ScaleDown.UnitsPolicyValue) + } + if behaviorJson.ScaleDown.PercentagePolicyValue != nil { + output.PercentagePolicyValue = fmt.Sprintf("%d", *behaviorJson.ScaleDown.PercentagePolicyValue) + } + if behaviorJson.ScaleDown.StabilizationWindow != nil { + output.StabilizationWindow = fmt.Sprintf("%d", *behaviorJson.ScaleDown.StabilizationWindow) + } + return output +} diff --git a/tsuru/client/autoscale.go b/tsuru/client/autoscale.go index d1ff422a..2fe24b8f 100644 --- a/tsuru/client/autoscale.go +++ b/tsuru/client/autoscale.go @@ -77,6 +77,15 @@ func (c *AutoScaleSet) Flags() *gnuflag.FlagSet { c.fs.Var(&c.schedules, "schedule", "Schedule window to up/down scale. Example: {\"minReplicas\": 2, \"start\": \"0 6 * * *\", \"end\": \"0 18 * * *\"}") c.fs.Var(&c.prometheus, "prometheus", "Prometheus settings to up/down scale. Example: {\"name\": \"my_metric_identification\", \"threshold\": 10, \"query\":\"sum(my_metric{tsuru_app=\\\"my_app\\\"})\"}") + + c.fs.Var((*int32Value)(&c.autoscale.Behavior.ScaleDown.PercentagePolicyValue), "scale-down-percentage", "Percentage of units to downscale when the metric is below the threshold") + c.fs.Var((*int32Value)(&c.autoscale.Behavior.ScaleDown.PercentagePolicyValue), "sdp", "Percentage of units to downscale when the metric is below the threshold") + + c.fs.Var((*int32Value)(&c.autoscale.Behavior.ScaleDown.StabilizationWindow), "scale-down-stabilization-window", "Stabilization window in seconds to avoid scale down") + c.fs.Var((*int32Value)(&c.autoscale.Behavior.ScaleDown.StabilizationWindow), "sdsw", "Stabilization window in seconds to avoid scale down") + + c.fs.Var((*int32Value)(&c.autoscale.Behavior.ScaleDown.UnitsPolicyValue), "scale-down-units", "Number of units to downscale when the metric is below the threshold") + c.fs.Var((*int32Value)(&c.autoscale.Behavior.ScaleDown.UnitsPolicyValue), "sdu", "Number of units to downscale when the metric is below the threshold") } return c.fs } @@ -90,31 +99,24 @@ func (c *AutoScaleSet) Run(ctx *cmd.Context) error { if err != nil { return err } - schedules := []tsuru.AutoScaleSchedule{} for _, scheduleString := range c.schedules { var autoScaleSchedule tsuru.AutoScaleSchedule if err = json.Unmarshal([]byte(scheduleString), &autoScaleSchedule); err != nil { return err } - schedules = append(schedules, autoScaleSchedule) } - c.autoscale.Schedules = schedules - prometheus := []tsuru.AutoScalePrometheus{} for _, prometheusString := range c.prometheus { var autoScalePrometheus tsuru.AutoScalePrometheus if err = json.Unmarshal([]byte(prometheusString), &autoScalePrometheus); err != nil { return err } - prometheus = append(prometheus, autoScalePrometheus) } - c.autoscale.Prometheus = prometheus - _, err = apiClient.AppApi.AutoScaleAdd(context.TODO(), appName, c.autoscale) if err != nil { return err diff --git a/tsuru/client/autoscale_test.go b/tsuru/client/autoscale_test.go index ba6941f9..7e7a229a 100644 --- a/tsuru/client/autoscale_test.go +++ b/tsuru/client/autoscale_test.go @@ -53,6 +53,77 @@ func (s *S) TestAutoScaleSet(c *check.C) { c.Assert(stdout.String(), check.Equals, expected) } +func (s *S) TestAutoScaleBehaviorSet(c *check.C) { + tests := []struct { + param []string + expected tsuru.AutoScaleSpecBehavior + }{ + { + param: []string{"-a", "myapp", "-p", "proc1", "--min", "2", "--max", "5", "--sdp", "3", "--sdsw", "22", "--sdu", "9"}, + expected: tsuru.AutoScaleSpecBehavior{ + ScaleDown: tsuru.AutoScaleSpecBehaviorScaleDown{ + StabilizationWindow: 22, + PercentagePolicyValue: 3, + UnitsPolicyValue: 9, + }, + }, + }, + { + param: []string{"-a", "myapp", "-p", "proc1", "--min", "2", "--max", "5", "--scale-down-percentage", "5", "--scale-down-stabilization-window", "7", "--scale-down-units", "40"}, + expected: tsuru.AutoScaleSpecBehavior{ + ScaleDown: tsuru.AutoScaleSpecBehaviorScaleDown{ + StabilizationWindow: 7, + PercentagePolicyValue: 5, + UnitsPolicyValue: 40, + }, + }, + }, + { + param: []string{"-a", "myapp", "-p", "proc1", "--min", "2", "--max", "5"}, + expected: tsuru.AutoScaleSpecBehavior{}, + }, + } + + for _, tt := range tests { + var stdout, stderr bytes.Buffer + expected := "Unit auto scale successfully set.\n" + context := cmd.Context{ + Stdout: &stdout, + Stderr: &stderr, + Args: []string{}, + } + trans := cmdtest.ConditionalTransport{ + Transport: cmdtest.Transport{Message: "", Status: http.StatusOK}, + CondFunc: func(r *http.Request) bool { + c.Assert(r.URL.Path, check.Equals, "/1.9/apps/myapp/units/autoscale") + c.Assert(r.Method, check.Equals, "POST") + var ret tsuru.AutoScaleSpec + c.Assert(r.Header.Get("Content-Type"), check.Equals, "application/json") + data, err := io.ReadAll(r.Body) + c.Assert(err, check.IsNil) + err = json.Unmarshal(data, &ret) + c.Assert(err, check.IsNil) + c.Assert(ret, check.DeepEquals, tsuru.AutoScaleSpec{ + MinUnits: 2, + MaxUnits: 5, + Process: "proc1", + Behavior: tsuru.AutoScaleSpecBehavior{ + ScaleDown: tt.expected.ScaleDown, + }, + }) + return true + }, + } + s.setupFakeTransport(&trans) + command := AutoScaleSet{} + command.Info() + command.Flags().Parse(true, tt.param) + err := command.Run(&context) + c.Assert(err, check.IsNil) + c.Assert(stdout.String(), check.Equals, expected) + } +} + func (s *S) TestKEDAScheduleAutoScaleSet(c *check.C) { var stdout, stderr bytes.Buffer expected := "Unit auto scale successfully set.\n"