Skip to content

Commit

Permalink
Feat/auto scale behavior client (#235)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
infezek authored Nov 5, 2024
1 parent 681fdbb commit 58042b7
Show file tree
Hide file tree
Showing 7 changed files with 233 additions and 58 deletions.
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -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=
Expand Down
9 changes: 8 additions & 1 deletion tsuru/client/apps.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}

Expand Down
138 changes: 91 additions & 47 deletions tsuru/client/apps_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
}
}
]
}`
Expand Down Expand Up @@ -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{
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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{
Expand Down
51 changes: 51 additions & 0 deletions tsuru/client/apps_type.go
Original file line number Diff line number Diff line change
@@ -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
}
16 changes: 9 additions & 7 deletions tsuru/client/autoscale.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand All @@ -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
Expand Down
71 changes: 71 additions & 0 deletions tsuru/client/autoscale_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down

0 comments on commit 58042b7

Please sign in to comment.