Skip to content

Commit 3aebde1

Browse files
authored
Feat/auto scale behavior (#63)
* feat: add scale down behavior to autoscaling configuration * chore: update go-tsuruclient dependency to latest version * feat: enhance scale down functionality in autoscaling configuration * feat: implement flatten scale down functionality for autoscaling * test: add unit tests for flatten scale down functionality * test: add acceptance tests for scale down functionality in autoscaling * chore: add VSCode settings for Go test environment variables * refactor: rename flattenScaleDown
1 parent fcf21e1 commit 3aebde1

7 files changed

+403
-4
lines changed

.vscode/settings.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"go.testEnvVars": {
3+
"TF_ACC": "1",
4+
"TF_ACC_TERRAFORM_VERSION": "1.4.4"
5+
}
6+
}

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ require (
99
github.com/labstack/echo/v4 v4.9.1
1010
github.com/pkg/errors v0.9.1
1111
github.com/stretchr/testify v1.9.0
12-
github.com/tsuru/go-tsuruclient v0.0.0-20240409125509-22a1e08326f4
12+
github.com/tsuru/go-tsuruclient v0.0.0-20241029183502-e219a905d873
1313
github.com/tsuru/tsuru-client v0.0.0-20240325204824-8c0dc602a5be
1414
k8s.io/apimachinery v0.26.2
1515
)

go.sum

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -206,8 +206,8 @@ github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsT
206206
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
207207
github.com/tsuru/gnuflag v0.0.0-20151217162021-86b8c1b864aa h1:JlLQP1xa13a994p/Aau2e3K9xXYaHNoNvTDVIMHSUa4=
208208
github.com/tsuru/gnuflag v0.0.0-20151217162021-86b8c1b864aa/go.mod h1:UibOSvkMFKRe/eiwktAPAvQG8L+p8nYsECJvu3Dgw7I=
209-
github.com/tsuru/go-tsuruclient v0.0.0-20240409125509-22a1e08326f4 h1:MGmG6AxKP8XRe7nQqIQR+Tsb5tCzHnYpYk0tiuXVgxY=
210-
github.com/tsuru/go-tsuruclient v0.0.0-20240409125509-22a1e08326f4/go.mod h1:qwh/KJ6ypa2GISRI79XFOHhnSjGOe1cZVPHF3nfrf18=
209+
github.com/tsuru/go-tsuruclient v0.0.0-20241029183502-e219a905d873 h1:Rs3urDCvqLpmGpUKOJNRiOCij/A+EcemdeOaGmGcs/E=
210+
github.com/tsuru/go-tsuruclient v0.0.0-20241029183502-e219a905d873/go.mod h1:qwh/KJ6ypa2GISRI79XFOHhnSjGOe1cZVPHF3nfrf18=
211211
github.com/tsuru/tablecli v0.0.0-20190131152944-7ded8a3383c6 h1:1XDdWFAjIbCSG1OjN9v9KdWhuM8UtYlFcfHe/Ldkchk=
212212
github.com/tsuru/tablecli v0.0.0-20190131152944-7ded8a3383c6/go.mod h1:ztYpOhW+u1k21FEqp7nZNgpWbr0dUKok5lgGCZi+1AQ=
213213
github.com/tsuru/tsuru v0.0.0-20240325190920-410c71393b77 h1:cuWFjNLaemdQZhojqJbb/rOXO97tlcPeLAHg/x+EQGk=

internal/provider/resource_tsuru_app_autoscale.go

Lines changed: 62 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,31 @@ func resourceTsuruApplicationAutoscale() *schema.Resource {
128128
Optional: true,
129129
AtLeastOneOf: []string{"cpu_average", "schedule", "prometheus"},
130130
},
131+
"scale_down": {
132+
Type: schema.TypeList,
133+
Description: "Behavior of the auto scale down",
134+
Elem: &schema.Resource{
135+
Schema: map[string]*schema.Schema{
136+
"units": {
137+
Type: schema.TypeInt,
138+
Optional: true,
139+
Description: "Number of units to scale down",
140+
},
141+
"percentage": {
142+
Type: schema.TypeInt,
143+
Optional: true,
144+
Description: "Percentage of units to scale down",
145+
},
146+
"stabilization_window": {
147+
Type: schema.TypeInt,
148+
Optional: true,
149+
Description: "Stabilization window in seconds",
150+
},
151+
},
152+
},
153+
Optional: true,
154+
AtLeastOneOf: []string{"cpu_average", "schedule", "prometheus"},
155+
},
131156
},
132157
}
133158
}
@@ -167,6 +192,9 @@ func resourceTsuruApplicationAutoscaleSet(ctx context.Context, d *schema.Resourc
167192
Process: process,
168193
MinUnits: int32(minUnits),
169194
MaxUnits: int32(maxUnits),
195+
Behavior: tsuru_client.AutoScaleSpecBehavior{
196+
ScaleDown: tsuru_client.AutoScaleSpecBehaviorScaleDown{},
197+
},
170198
}
171199

172200
if cpu, ok := d.GetOk("cpu_average"); ok {
@@ -186,6 +214,10 @@ func resourceTsuruApplicationAutoscaleSet(ctx context.Context, d *schema.Resourc
186214
autoscale.Prometheus = prometheus
187215
}
188216
}
217+
if m, ok := d.GetOk("scale_down"); ok {
218+
scaleDown := scaleDownFromResourceData(m)
219+
autoscale.Behavior.ScaleDown = scaleDown
220+
}
189221

190222
err = resource.RetryContext(ctx, d.Timeout(schema.TimeoutCreate), func() *resource.RetryError {
191223
_, err = provider.TsuruClient.AppApi.AutoScaleAdd(ctx, app, autoscale)
@@ -225,6 +257,8 @@ func resourceTsuruApplicationAutoscaleRead(ctx context.Context, d *schema.Resour
225257

226258
retryCount := 0
227259
maxRetries := 5
260+
261+
_, proposed := d.GetChange("scale_down")
228262
// autoscale info reflects near realtime
229263
err = resource.RetryContext(ctx, d.Timeout(schema.TimeoutCreate), func() *resource.RetryError {
230264
retryCount++
@@ -254,7 +288,7 @@ func resourceTsuruApplicationAutoscaleRead(ctx context.Context, d *schema.Resour
254288

255289
d.Set("schedule", flattenSchedules(autoscale.Schedules))
256290
d.Set("prometheus", flattenPrometheus(autoscale.Prometheus, d))
257-
291+
d.Set("scale_down", flattenScaleDown(autoscale.Behavior.ScaleDown, proposed))
258292
return nil
259293
}
260294

@@ -394,6 +428,33 @@ func prometheusFromResourceData(meta interface{}) []tsuru_client.AutoScalePromet
394428
return prometheus
395429
}
396430

431+
func scaleDownFromResourceData(meta interface{}) tsuru_client.AutoScaleSpecBehaviorScaleDown {
432+
scaleDownMeta := meta.([]interface{})
433+
if len(scaleDownMeta) == 0 {
434+
return tsuru_client.AutoScaleSpecBehaviorScaleDown{}
435+
}
436+
scaleDown := tsuru_client.AutoScaleSpecBehaviorScaleDown{}
437+
for _, iFace := range scaleDownMeta {
438+
sd := iFace.(map[string]interface{})
439+
if v, ok := sd["percentage"]; ok {
440+
if val, ok := v.(int); ok {
441+
scaleDown.PercentagePolicyValue = int32(val)
442+
}
443+
}
444+
if v, ok := sd["units"]; ok {
445+
if val, ok := v.(int); ok {
446+
scaleDown.UnitsPolicyValue = int32(val)
447+
}
448+
}
449+
if v, ok := sd["stabilization_window"]; ok {
450+
if val, ok := v.(int); ok {
451+
scaleDown.StabilizationWindow = int32(val)
452+
}
453+
}
454+
}
455+
return scaleDown
456+
}
457+
397458
func flattenSchedules(schedules []tsuru_client.AutoScaleSchedule) []interface{} {
398459
result := []interface{}{}
399460

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
// Copyright 2024 tsuru authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
package provider
6+
7+
import (
8+
"fmt"
9+
"reflect"
10+
11+
tsuru_client "github.com/tsuru/go-tsuruclient/pkg/tsuru"
12+
)
13+
14+
type flattenScaleDownBehavior struct {
15+
PERCENTAGE_VALUE int32
16+
PERCENTAGE_LABEL string
17+
STABILIZATION_WINDOW_VALUE int32
18+
STABILIZATION_WINDOW_LABEL string
19+
UNITS_VALUE int32
20+
UNITS_LABEL string
21+
ScaleDownRead tsuru_client.AutoScaleSpecBehaviorScaleDown
22+
Proposed interface{}
23+
}
24+
25+
func flattenScaleDown(scaleDownRead tsuru_client.AutoScaleSpecBehaviorScaleDown, proposed interface{}) interface{} {
26+
fsd := &flattenScaleDownBehavior{
27+
PERCENTAGE_VALUE: 10,
28+
PERCENTAGE_LABEL: "percentage",
29+
STABILIZATION_WINDOW_VALUE: 300,
30+
STABILIZATION_WINDOW_LABEL: "stabilization_window",
31+
UNITS_VALUE: 3,
32+
UNITS_LABEL: "units",
33+
ScaleDownRead: scaleDownRead,
34+
Proposed: proposed,
35+
}
36+
return fsd.execute()
37+
}
38+
39+
func (fsd *flattenScaleDownBehavior) execute() interface{} {
40+
if fsd.ScaleDownRead == (tsuru_client.AutoScaleSpecBehaviorScaleDown{}) {
41+
return nil
42+
}
43+
proposedList, err := fsd.convertToMapSlice(fsd.Proposed)
44+
if err != nil {
45+
return []map[string]interface{}{{
46+
"percentage": fsd.ScaleDownRead.PercentagePolicyValue,
47+
"stabilization_window": fsd.ScaleDownRead.StabilizationWindow,
48+
"units": fsd.ScaleDownRead.UnitsPolicyValue,
49+
}}
50+
}
51+
if value, ok := fsd.noInputParameters(proposedList); ok {
52+
return value
53+
}
54+
return fsd.withInputParameters(proposedList)
55+
}
56+
57+
func (fsd *flattenScaleDownBehavior) withInputParameters(proposedList []map[string]interface{}) (value []map[string]interface{}) {
58+
scaleDownCurrent := []map[string]interface{}{{}}
59+
percentage, ok := fsd.findScaleDownInProposedList(proposedList, fsd.PERCENTAGE_LABEL)
60+
if ok && percentage != 0 || fsd.ScaleDownRead.PercentagePolicyValue != int32(fsd.PERCENTAGE_VALUE) {
61+
scaleDownCurrent[0][fsd.PERCENTAGE_LABEL] = fsd.ScaleDownRead.PercentagePolicyValue
62+
}
63+
stabilizationWindow, ok := fsd.findScaleDownInProposedList(proposedList, fsd.STABILIZATION_WINDOW_LABEL)
64+
if ok && stabilizationWindow != 0 || fsd.ScaleDownRead.StabilizationWindow != int32(fsd.STABILIZATION_WINDOW_VALUE) {
65+
scaleDownCurrent[0][fsd.STABILIZATION_WINDOW_LABEL] = fsd.ScaleDownRead.StabilizationWindow
66+
}
67+
units, ok := fsd.findScaleDownInProposedList(proposedList, fsd.UNITS_LABEL)
68+
if ok && units != 0 || fsd.ScaleDownRead.UnitsPolicyValue != int32(fsd.UNITS_VALUE) {
69+
scaleDownCurrent[0][fsd.UNITS_LABEL] = fsd.ScaleDownRead.UnitsPolicyValue
70+
}
71+
return scaleDownCurrent
72+
}
73+
74+
func (fsd *flattenScaleDownBehavior) noInputParameters(proposedList []map[string]interface{}) (value interface{}, ok bool) {
75+
if len(proposedList) != 0 {
76+
return nil, false
77+
}
78+
scaleDownCurrent := []map[string]interface{}{{}}
79+
if fsd.ScaleDownRead.PercentagePolicyValue != fsd.PERCENTAGE_VALUE {
80+
scaleDownCurrent[0][fsd.PERCENTAGE_LABEL] = fsd.ScaleDownRead.PercentagePolicyValue
81+
}
82+
if fsd.ScaleDownRead.StabilizationWindow != fsd.STABILIZATION_WINDOW_VALUE {
83+
scaleDownCurrent[0][fsd.STABILIZATION_WINDOW_LABEL] = fsd.ScaleDownRead.StabilizationWindow
84+
}
85+
if fsd.ScaleDownRead.UnitsPolicyValue != fsd.UNITS_VALUE {
86+
scaleDownCurrent[0][fsd.UNITS_LABEL] = fsd.ScaleDownRead.UnitsPolicyValue
87+
}
88+
if fsd.isScaleDownEmpty(scaleDownCurrent) {
89+
return nil, true
90+
}
91+
return scaleDownCurrent, true
92+
}
93+
94+
func (fsd *flattenScaleDownBehavior) findScaleDownInProposedList(proposedList []map[string]interface{}, key string) (value int, ok bool) {
95+
for _, item := range proposedList {
96+
if v, ok := item[key]; ok {
97+
return v.(int), true
98+
}
99+
}
100+
return 0, false
101+
}
102+
103+
func (fsd *flattenScaleDownBehavior) convertToMapSlice(input interface{}) ([]map[string]interface{}, error) {
104+
var result []map[string]interface{}
105+
if reflect.TypeOf(input).Kind() != reflect.Slice {
106+
return nil, fmt.Errorf("scale down: invalid input type, slice expected")
107+
}
108+
for _, item := range input.([]interface{}) {
109+
if mapItem, ok := item.(map[string]interface{}); ok {
110+
result = append(result, mapItem)
111+
} else {
112+
return []map[string]interface{}{}, nil
113+
}
114+
}
115+
return result, nil
116+
}
117+
118+
func (fsd *flattenScaleDownBehavior) isScaleDownEmpty(param []map[string]interface{}) bool {
119+
if len(param) != 1 {
120+
return false
121+
}
122+
if _, ok := param[0][fsd.PERCENTAGE_LABEL]; ok {
123+
return false
124+
}
125+
if _, ok := param[0][fsd.STABILIZATION_WINDOW_LABEL]; ok {
126+
return false
127+
}
128+
if _, ok := param[0][fsd.UNITS_LABEL]; ok {
129+
return false
130+
}
131+
return true
132+
}
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
// Copyright 2024 tsuru authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
package provider
6+
7+
import (
8+
"testing"
9+
10+
"github.com/stretchr/testify/assert"
11+
tsuru_client "github.com/tsuru/go-tsuruclient/pkg/tsuru"
12+
)
13+
14+
func TestFluentDown(t *testing.T) {
15+
assert := assert.New(t)
16+
tests := []struct {
17+
scaleDownRead tsuru_client.AutoScaleSpecBehaviorScaleDown
18+
scaleDownInput interface{}
19+
expected interface{}
20+
}{
21+
{
22+
scaleDownRead: tsuru_client.AutoScaleSpecBehaviorScaleDown{
23+
UnitsPolicyValue: 3,
24+
PercentagePolicyValue: 10,
25+
StabilizationWindow: 300,
26+
},
27+
scaleDownInput: []interface{}{},
28+
expected: nil,
29+
},
30+
31+
{
32+
scaleDownRead: tsuru_client.AutoScaleSpecBehaviorScaleDown{
33+
UnitsPolicyValue: 3,
34+
PercentagePolicyValue: 10,
35+
StabilizationWindow: 300,
36+
},
37+
scaleDownInput: []interface{}{
38+
map[string]interface{}{"units": 3},
39+
},
40+
expected: []map[string]interface{}{{
41+
"units": int32(3),
42+
}},
43+
},
44+
{
45+
scaleDownRead: tsuru_client.AutoScaleSpecBehaviorScaleDown{
46+
UnitsPolicyValue: 3,
47+
PercentagePolicyValue: 10,
48+
StabilizationWindow: 300,
49+
},
50+
scaleDownInput: []interface{}{
51+
map[string]interface{}{"units": 3},
52+
map[string]interface{}{"stabilization_window": 300},
53+
map[string]interface{}{"percentage": 10},
54+
},
55+
expected: []map[string]interface{}{{
56+
"units": int32(3),
57+
"stabilization_window": int32(300),
58+
"percentage": int32(10),
59+
}},
60+
},
61+
{
62+
scaleDownRead: tsuru_client.AutoScaleSpecBehaviorScaleDown{
63+
UnitsPolicyValue: 21,
64+
PercentagePolicyValue: 21,
65+
StabilizationWindow: 21,
66+
},
67+
scaleDownInput: []interface{}{
68+
map[string]interface{}{"units": 3},
69+
map[string]interface{}{"stabilization_window": 300},
70+
map[string]interface{}{"percentage": 10},
71+
},
72+
expected: []map[string]interface{}{{
73+
"units": int32(21),
74+
"stabilization_window": int32(21),
75+
"percentage": int32(21),
76+
}},
77+
},
78+
{
79+
scaleDownRead: tsuru_client.AutoScaleSpecBehaviorScaleDown{
80+
UnitsPolicyValue: 21,
81+
PercentagePolicyValue: 21,
82+
StabilizationWindow: 21,
83+
},
84+
scaleDownInput: []interface{}{},
85+
expected: []map[string]interface{}{{
86+
"units": int32(21),
87+
"stabilization_window": int32(21),
88+
"percentage": int32(21),
89+
}},
90+
},
91+
}
92+
for _, test := range tests {
93+
readToDiff := flattenScaleDown(test.scaleDownRead, test.scaleDownInput)
94+
assert.Equal(test.expected, readToDiff)
95+
}
96+
}

0 commit comments

Comments
 (0)