Skip to content

Commit

Permalink
Feat/auto scale behavior (#63)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
infezek authored Dec 11, 2024
1 parent fcf21e1 commit 3aebde1
Show file tree
Hide file tree
Showing 7 changed files with 403 additions and 4 deletions.
6 changes: 6 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"go.testEnvVars": {
"TF_ACC": "1",
"TF_ACC_TERRAFORM_VERSION": "1.4.4"
}
}
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ require (
github.com/labstack/echo/v4 v4.9.1
github.com/pkg/errors v0.9.1
github.com/stretchr/testify v1.9.0
github.com/tsuru/go-tsuruclient v0.0.0-20240409125509-22a1e08326f4
github.com/tsuru/go-tsuruclient v0.0.0-20241029183502-e219a905d873
github.com/tsuru/tsuru-client v0.0.0-20240325204824-8c0dc602a5be
k8s.io/apimachinery v0.26.2
)
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -206,8 +206,8 @@ github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsT
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
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-20240409125509-22a1e08326f4 h1:MGmG6AxKP8XRe7nQqIQR+Tsb5tCzHnYpYk0tiuXVgxY=
github.com/tsuru/go-tsuruclient v0.0.0-20240409125509-22a1e08326f4/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-20240325190920-410c71393b77 h1:cuWFjNLaemdQZhojqJbb/rOXO97tlcPeLAHg/x+EQGk=
Expand Down
63 changes: 62 additions & 1 deletion internal/provider/resource_tsuru_app_autoscale.go
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,31 @@ func resourceTsuruApplicationAutoscale() *schema.Resource {
Optional: true,
AtLeastOneOf: []string{"cpu_average", "schedule", "prometheus"},
},
"scale_down": {
Type: schema.TypeList,
Description: "Behavior of the auto scale down",
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"units": {
Type: schema.TypeInt,
Optional: true,
Description: "Number of units to scale down",
},
"percentage": {
Type: schema.TypeInt,
Optional: true,
Description: "Percentage of units to scale down",
},
"stabilization_window": {
Type: schema.TypeInt,
Optional: true,
Description: "Stabilization window in seconds",
},
},
},
Optional: true,
AtLeastOneOf: []string{"cpu_average", "schedule", "prometheus"},
},
},
}
}
Expand Down Expand Up @@ -167,6 +192,9 @@ func resourceTsuruApplicationAutoscaleSet(ctx context.Context, d *schema.Resourc
Process: process,
MinUnits: int32(minUnits),
MaxUnits: int32(maxUnits),
Behavior: tsuru_client.AutoScaleSpecBehavior{
ScaleDown: tsuru_client.AutoScaleSpecBehaviorScaleDown{},
},
}

if cpu, ok := d.GetOk("cpu_average"); ok {
Expand All @@ -186,6 +214,10 @@ func resourceTsuruApplicationAutoscaleSet(ctx context.Context, d *schema.Resourc
autoscale.Prometheus = prometheus
}
}
if m, ok := d.GetOk("scale_down"); ok {
scaleDown := scaleDownFromResourceData(m)
autoscale.Behavior.ScaleDown = scaleDown
}

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

retryCount := 0
maxRetries := 5

_, proposed := d.GetChange("scale_down")
// autoscale info reflects near realtime
err = resource.RetryContext(ctx, d.Timeout(schema.TimeoutCreate), func() *resource.RetryError {
retryCount++
Expand Down Expand Up @@ -254,7 +288,7 @@ func resourceTsuruApplicationAutoscaleRead(ctx context.Context, d *schema.Resour

d.Set("schedule", flattenSchedules(autoscale.Schedules))
d.Set("prometheus", flattenPrometheus(autoscale.Prometheus, d))

d.Set("scale_down", flattenScaleDown(autoscale.Behavior.ScaleDown, proposed))
return nil
}

Expand Down Expand Up @@ -394,6 +428,33 @@ func prometheusFromResourceData(meta interface{}) []tsuru_client.AutoScalePromet
return prometheus
}

func scaleDownFromResourceData(meta interface{}) tsuru_client.AutoScaleSpecBehaviorScaleDown {
scaleDownMeta := meta.([]interface{})
if len(scaleDownMeta) == 0 {
return tsuru_client.AutoScaleSpecBehaviorScaleDown{}
}
scaleDown := tsuru_client.AutoScaleSpecBehaviorScaleDown{}
for _, iFace := range scaleDownMeta {
sd := iFace.(map[string]interface{})
if v, ok := sd["percentage"]; ok {
if val, ok := v.(int); ok {
scaleDown.PercentagePolicyValue = int32(val)
}
}
if v, ok := sd["units"]; ok {
if val, ok := v.(int); ok {
scaleDown.UnitsPolicyValue = int32(val)
}
}
if v, ok := sd["stabilization_window"]; ok {
if val, ok := v.(int); ok {
scaleDown.StabilizationWindow = int32(val)
}
}
}
return scaleDown
}

func flattenSchedules(schedules []tsuru_client.AutoScaleSchedule) []interface{} {
result := []interface{}{}

Expand Down
132 changes: 132 additions & 0 deletions internal/provider/resource_tsuru_app_autoscale_flatten.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
// Copyright 2024 tsuru 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 provider

import (
"fmt"
"reflect"

tsuru_client "github.com/tsuru/go-tsuruclient/pkg/tsuru"
)

type flattenScaleDownBehavior struct {
PERCENTAGE_VALUE int32
PERCENTAGE_LABEL string
STABILIZATION_WINDOW_VALUE int32
STABILIZATION_WINDOW_LABEL string
UNITS_VALUE int32
UNITS_LABEL string
ScaleDownRead tsuru_client.AutoScaleSpecBehaviorScaleDown
Proposed interface{}
}

func flattenScaleDown(scaleDownRead tsuru_client.AutoScaleSpecBehaviorScaleDown, proposed interface{}) interface{} {
fsd := &flattenScaleDownBehavior{
PERCENTAGE_VALUE: 10,
PERCENTAGE_LABEL: "percentage",
STABILIZATION_WINDOW_VALUE: 300,
STABILIZATION_WINDOW_LABEL: "stabilization_window",
UNITS_VALUE: 3,
UNITS_LABEL: "units",
ScaleDownRead: scaleDownRead,
Proposed: proposed,
}
return fsd.execute()
}

func (fsd *flattenScaleDownBehavior) execute() interface{} {
if fsd.ScaleDownRead == (tsuru_client.AutoScaleSpecBehaviorScaleDown{}) {
return nil
}
proposedList, err := fsd.convertToMapSlice(fsd.Proposed)
if err != nil {
return []map[string]interface{}{{
"percentage": fsd.ScaleDownRead.PercentagePolicyValue,
"stabilization_window": fsd.ScaleDownRead.StabilizationWindow,
"units": fsd.ScaleDownRead.UnitsPolicyValue,
}}
}
if value, ok := fsd.noInputParameters(proposedList); ok {
return value
}
return fsd.withInputParameters(proposedList)
}

func (fsd *flattenScaleDownBehavior) withInputParameters(proposedList []map[string]interface{}) (value []map[string]interface{}) {
scaleDownCurrent := []map[string]interface{}{{}}
percentage, ok := fsd.findScaleDownInProposedList(proposedList, fsd.PERCENTAGE_LABEL)
if ok && percentage != 0 || fsd.ScaleDownRead.PercentagePolicyValue != int32(fsd.PERCENTAGE_VALUE) {
scaleDownCurrent[0][fsd.PERCENTAGE_LABEL] = fsd.ScaleDownRead.PercentagePolicyValue
}
stabilizationWindow, ok := fsd.findScaleDownInProposedList(proposedList, fsd.STABILIZATION_WINDOW_LABEL)
if ok && stabilizationWindow != 0 || fsd.ScaleDownRead.StabilizationWindow != int32(fsd.STABILIZATION_WINDOW_VALUE) {
scaleDownCurrent[0][fsd.STABILIZATION_WINDOW_LABEL] = fsd.ScaleDownRead.StabilizationWindow
}
units, ok := fsd.findScaleDownInProposedList(proposedList, fsd.UNITS_LABEL)
if ok && units != 0 || fsd.ScaleDownRead.UnitsPolicyValue != int32(fsd.UNITS_VALUE) {
scaleDownCurrent[0][fsd.UNITS_LABEL] = fsd.ScaleDownRead.UnitsPolicyValue
}
return scaleDownCurrent
}

func (fsd *flattenScaleDownBehavior) noInputParameters(proposedList []map[string]interface{}) (value interface{}, ok bool) {
if len(proposedList) != 0 {
return nil, false
}
scaleDownCurrent := []map[string]interface{}{{}}
if fsd.ScaleDownRead.PercentagePolicyValue != fsd.PERCENTAGE_VALUE {
scaleDownCurrent[0][fsd.PERCENTAGE_LABEL] = fsd.ScaleDownRead.PercentagePolicyValue
}
if fsd.ScaleDownRead.StabilizationWindow != fsd.STABILIZATION_WINDOW_VALUE {
scaleDownCurrent[0][fsd.STABILIZATION_WINDOW_LABEL] = fsd.ScaleDownRead.StabilizationWindow
}
if fsd.ScaleDownRead.UnitsPolicyValue != fsd.UNITS_VALUE {
scaleDownCurrent[0][fsd.UNITS_LABEL] = fsd.ScaleDownRead.UnitsPolicyValue
}
if fsd.isScaleDownEmpty(scaleDownCurrent) {
return nil, true
}
return scaleDownCurrent, true
}

func (fsd *flattenScaleDownBehavior) findScaleDownInProposedList(proposedList []map[string]interface{}, key string) (value int, ok bool) {
for _, item := range proposedList {
if v, ok := item[key]; ok {
return v.(int), true
}
}
return 0, false
}

func (fsd *flattenScaleDownBehavior) convertToMapSlice(input interface{}) ([]map[string]interface{}, error) {
var result []map[string]interface{}
if reflect.TypeOf(input).Kind() != reflect.Slice {
return nil, fmt.Errorf("scale down: invalid input type, slice expected")
}
for _, item := range input.([]interface{}) {
if mapItem, ok := item.(map[string]interface{}); ok {
result = append(result, mapItem)
} else {
return []map[string]interface{}{}, nil
}
}
return result, nil
}

func (fsd *flattenScaleDownBehavior) isScaleDownEmpty(param []map[string]interface{}) bool {
if len(param) != 1 {
return false
}
if _, ok := param[0][fsd.PERCENTAGE_LABEL]; ok {
return false
}
if _, ok := param[0][fsd.STABILIZATION_WINDOW_LABEL]; ok {
return false
}
if _, ok := param[0][fsd.UNITS_LABEL]; ok {
return false
}
return true
}
96 changes: 96 additions & 0 deletions internal/provider/resource_tsuru_app_autoscale_scale_down_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
// Copyright 2024 tsuru 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 provider

import (
"testing"

"github.com/stretchr/testify/assert"
tsuru_client "github.com/tsuru/go-tsuruclient/pkg/tsuru"
)

func TestFluentDown(t *testing.T) {
assert := assert.New(t)
tests := []struct {
scaleDownRead tsuru_client.AutoScaleSpecBehaviorScaleDown
scaleDownInput interface{}
expected interface{}
}{
{
scaleDownRead: tsuru_client.AutoScaleSpecBehaviorScaleDown{
UnitsPolicyValue: 3,
PercentagePolicyValue: 10,
StabilizationWindow: 300,
},
scaleDownInput: []interface{}{},
expected: nil,
},

{
scaleDownRead: tsuru_client.AutoScaleSpecBehaviorScaleDown{
UnitsPolicyValue: 3,
PercentagePolicyValue: 10,
StabilizationWindow: 300,
},
scaleDownInput: []interface{}{
map[string]interface{}{"units": 3},
},
expected: []map[string]interface{}{{
"units": int32(3),
}},
},
{
scaleDownRead: tsuru_client.AutoScaleSpecBehaviorScaleDown{
UnitsPolicyValue: 3,
PercentagePolicyValue: 10,
StabilizationWindow: 300,
},
scaleDownInput: []interface{}{
map[string]interface{}{"units": 3},
map[string]interface{}{"stabilization_window": 300},
map[string]interface{}{"percentage": 10},
},
expected: []map[string]interface{}{{
"units": int32(3),
"stabilization_window": int32(300),
"percentage": int32(10),
}},
},
{
scaleDownRead: tsuru_client.AutoScaleSpecBehaviorScaleDown{
UnitsPolicyValue: 21,
PercentagePolicyValue: 21,
StabilizationWindow: 21,
},
scaleDownInput: []interface{}{
map[string]interface{}{"units": 3},
map[string]interface{}{"stabilization_window": 300},
map[string]interface{}{"percentage": 10},
},
expected: []map[string]interface{}{{
"units": int32(21),
"stabilization_window": int32(21),
"percentage": int32(21),
}},
},
{
scaleDownRead: tsuru_client.AutoScaleSpecBehaviorScaleDown{
UnitsPolicyValue: 21,
PercentagePolicyValue: 21,
StabilizationWindow: 21,
},
scaleDownInput: []interface{}{},
expected: []map[string]interface{}{{
"units": int32(21),
"stabilization_window": int32(21),
"percentage": int32(21),
}},
},
}
for _, test := range tests {
readToDiff := flattenScaleDown(test.scaleDownRead, test.scaleDownInput)
assert.Equal(test.expected, readToDiff)
}
}
Loading

0 comments on commit 3aebde1

Please sign in to comment.