Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat/schedule autoscale #220

Merged
merged 6 commits into from
Dec 20, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,14 @@ require (
github.com/antihax/optional v1.0.0
github.com/ghodss/yaml v1.0.0
github.com/iancoleman/orderedmap v0.2.0
github.com/lnquy/cron v1.1.1
github.com/mattn/go-shellwords v1.0.12
github.com/mitchellh/go-wordwrap v1.0.1
github.com/pkg/errors v0.9.1
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-20231031185823-b0abac462f59
github.com/tsuru/go-tsuruclient v0.0.0-20231124151049-7b8d8ea2ee30
github.com/tsuru/tablecli v0.0.0-20190131152944-7ded8a3383c6
github.com/tsuru/tsuru v0.0.0-20231009130140-65592312e508
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c
Expand Down
6 changes: 4 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -502,6 +502,8 @@ github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/linuxkit/virtsock v0.0.0-20201010232012-f8cee7dfc7a3/go.mod h1:3r6x7q95whyfWQpmGZTu3gk3v2YkMi05HEzl7Tf7YEo=
github.com/lnquy/cron v1.1.1 h1:iaDX1ublgQ9LBhA8l9BVU+FrTE1PPSPAuvAdhgdnXgA=
github.com/lnquy/cron v1.1.1/go.mod h1:hu2Y7H68/8oKk6T4+K4qdbopbnaP4rGltK3ylWiiDss=
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/mailru/easyjson v0.0.0-20160728113105-d5b7844b561a/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
Expand Down Expand Up @@ -733,8 +735,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-20231031185823-b0abac462f59 h1:RgFlupHEAJvIXH6FgtzGQqG8pS6ck3miqEXZN+a4dLs=
github.com/tsuru/go-tsuruclient v0.0.0-20231031185823-b0abac462f59/go.mod h1:BmePxHey9hxrxk0kzTMHFFr7aJWXSxtlrUx6FIeV0Ic=
github.com/tsuru/go-tsuruclient v0.0.0-20231124151049-7b8d8ea2ee30 h1:lPzNgSgTz+27YI5vCwEAGENgzNYb0gNtYcke0WuQsbk=
github.com/tsuru/go-tsuruclient v0.0.0-20231124151049-7b8d8ea2ee30/go.mod h1:BmePxHey9hxrxk0kzTMHFFr7aJWXSxtlrUx6FIeV0Ic=
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-20231009130140-65592312e508 h1:eaMg/uBeTv6B7O+AMTq3OKqNsiA0/kB5Zkgy7ipXgcI=
Expand Down
65 changes: 54 additions & 11 deletions tsuru/client/apps.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import (
"time"

"github.com/ajg/form"
"github.com/lnquy/cron"
"github.com/tsuru/gnuflag"
"github.com/tsuru/go-tsuruclient/pkg/client"
"github.com/tsuru/go-tsuruclient/pkg/tsuru"
Expand Down Expand Up @@ -744,22 +745,52 @@ func (a *app) String(simplified bool) string {
renderServiceInstanceBinds(&buf, a.ServiceInstanceBinds)
}

autoScaleTable := tablecli.NewTable()
autoScaleTable.Headers = tablecli.Row([]string{"Process", "Min", "Max", "Target CPU"})
var autoScaleTables []*tablecli.Table
processes := []string{}

for _, as := range a.AutoScale {
cpu := cpuValue(as.AverageCPU)
autoScaleTable.AddRow(tablecli.Row([]string{
fmt.Sprintf("%s (v%d)", as.Process, as.Version),
strconv.Itoa(int(as.MinUnits)),
strconv.Itoa(int(as.MaxUnits)),
cpu,
}))
autoScaleTable := tablecli.NewTable()
autoScaleTable.LineSeparator = true

processString := fmt.Sprintf(
"Process: %s (v%d), Min Units: %d, Max Units: %d",
as.Process, as.Version, int(as.MinUnits), int(as.MaxUnits),
)
processes = append(processes, processString)

autoScaleTable.Headers = tablecli.Row([]string{
"Triggers",
"Trigger details",
})

if as.AverageCPU != "" {
cpu := cpuValue(as.AverageCPU)
autoScaleTable.AddRow(tablecli.Row([]string{
"CPU",
fmt.Sprintf("Target: %s", cpu),
}))
}

for _, schedule := range as.Schedules {
scheduleInfo := buildScheduleInfo(schedule)
autoScaleTable.AddRow(tablecli.Row([]string{
"Schedule",
scheduleInfo,
}))
}

autoScaleTables = append(autoScaleTables, autoScaleTable)
}

if autoScaleTable.Rows() > 0 {
if len(processes) > 0 {
buf.WriteString("\n")
buf.WriteString("Auto Scale:\n")
buf.WriteString(autoScaleTable.String())
for i, asTable := range autoScaleTables {
buf.WriteString("\n")
buf.WriteString(processes[i])
buf.WriteString("\n")
buf.WriteString(asTable.String())
}
}

if !simplified && (a.Plan.Memory != 0 || a.Plan.CPUMilli != 0) {
Expand Down Expand Up @@ -790,6 +821,18 @@ func (a *app) String(simplified bool) string {
return tplBuffer.String() + buf.String()
}

func buildScheduleInfo(schedule tsuru.AutoScaleSchedule) string {
// Init with default EN locale
exprDesc, _ := cron.NewDescriptor()

startTimeHuman, _ := exprDesc.ToDescription(schedule.Start, cron.Locale_en)
endTimeHuman, _ := exprDesc.ToDescription(schedule.End, cron.Locale_en)

return fmt.Sprintf("Start: %s (%s)\nEnd: %s (%s)\nUnits: %d\nTimezone: %s",
startTimeHuman, schedule.Start, endTimeHuman, schedule.End, schedule.MinReplicas, schedule.Timezone,
)
}

func (a *app) SimpleServicesView() string {
sibs := make([]tsuru.AppServiceInstanceBinds, len(a.ServiceInstanceBinds))
copy(sibs, a.ServiceInstanceBinds)
Expand Down
157 changes: 151 additions & 6 deletions tsuru/client/apps_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1606,12 +1606,157 @@ Units [process worker]: 1
+--------+---------+------+------+

Auto Scale:
+--------------+-----+-----+------------+
| Process | Min | Max | Target CPU |
+--------------+-----+-----+------------+
| web (v10) | 1 | 10 | 50% |
| worker (v10) | 2 | 5 | 200% |
+--------------+-----+-----+------------+

Process: web (v10), Min Units: 1, Max Units: 10
+----------+-----------------+
| Triggers | Trigger details |
+----------+-----------------+
| CPU | Target: 50% |
+----------+-----------------+

Process: worker (v10), Min Units: 2, Max Units: 5
+----------+-----------------+
| Triggers | Trigger details |
+----------+-----------------+
| CPU | Target: 200% |
+----------+-----------------+

`
context := cmd.Context{
Stdout: &stdout,
Stderr: &stderr,
}
client := cmd.NewClient(&http.Client{Transport: &cmdtest.Transport{Message: result, Status: http.StatusOK}}, nil, manager)
command := AppInfo{}
command.Flags().Parse(true, []string{"--app", "app1"})
err := command.Run(&context, client)
c.Assert(err, check.IsNil)
c.Assert(stdout.String(), check.Equals, expected)
}

func (s *S) TestAppInfoWithKEDAAutoScale(c *check.C) {
var stdout, stderr bytes.Buffer
result := `{
"name": "app1",
"teamowner": "myteam",
"cname": [
""
],
"ip": "myapp.tsuru.io",
"platform": "php",
"repository": "git@git.com:php.git",
"state": "dead",
"units": [
{
"ID": "app1/0",
"Status": "started",
"ProcessName": "web"
},
{
"ID": "app1/1",
"Status": "started",
"ProcessName": "worker"
}
],
"teams": [
"tsuruteam",
"crane"
],
"owner": "myapp_owner",
"deploys": 7,
"router": "planb",
"autoscale": [
{
"process":"web",
"minUnits":1,
"maxUnits":10,
"averageCPU":"500m",
"version":10,
"schedules": [
{
"minReplicas":2,
"start":"0 6 * * *",
"end":"0 18 * * *",
"timezone":"UTC"
},{
"minReplicas":3,
"start":"0 12 * * *",
"end":"0 15 * * *",
"timezone":"UTC"
}
]
},
{
"process":"worker",
"minUnits":2,
"maxUnits":5,
"averageCPU":"2",
"version":10,
"schedules": [
{
"minReplicas":1,
"start":"0 0 * * *",
"end":"0 6 * * *",
"timezone":"America/Sao_Paulo"
}
]
}
]
}`
expected := `Application: app1
Platform: php
Router: planb
Teams: myteam (owner), tsuruteam, crane
External Addresses: myapp.tsuru.io
Created by: myapp_owner
Deploys: 7
Pool:
Quota: 0/0 units

Units [process web]: 1
+--------+---------+------+------+
| Name | Status | Host | Port |
+--------+---------+------+------+
| app1/0 | started | | |
+--------+---------+------+------+

Units [process worker]: 1
+--------+---------+------+------+
| Name | Status | Host | Port |
+--------+---------+------+------+
| app1/1 | started | | |
+--------+---------+------+------+

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 |
+----------+---------------------------------+

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 |
+----------+--------------------------------+

`
context := cmd.Context{
Expand Down
34 changes: 31 additions & 3 deletions tsuru/client/autoscale.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ package client

import (
"context"
"encoding/json"
"fmt"
"strconv"

Expand All @@ -29,13 +30,26 @@ type AutoScaleSet struct {
cmd.AppNameMixIn
fs *gnuflag.FlagSet
autoscale tsuru.AutoScaleSpec
schedules cmd.StringSliceFlag
}

func (c *AutoScaleSet) Info() *cmd.Info {
return &cmd.Info{
Name: "unit-autoscale-set",
Usage: "unit autoscale set [-a/--app appname] [-p/--process processname] [--cpu targetCPU] [--min minUnits] [--max maxUnits]",
Desc: `Sets a unit auto scale configuration.`,
Name: "unit-autoscale-set",
Usage: "unit autoscale set [-a/--app appname] [-p/--process processname] [--cpu targetCPU] [--min minUnits] [--max maxUnits] [--schedule scheduleWindow]",
Desc: `
# Sets an autoscale configuration:
# Based on 50% of CPU utilization with min units 1 and max units 3
unit autoscale set -a my-app --cpu 50% --min 1 --max 3

# Based on a schedule window everyday from 6AM to 6PM UTC
unit autoscale set -a my-app --min 1 --max 3 --schedule '{"minReplicas": 2, "start": "0 6 * * *", "end": "0 18 * * *"}'

# Combining both
unit autoscale set -a my-app --cpu 50% --min 1 --max 3 --schedule '{"minReplicas": 2, "start": "0 6 * * *", "end": "0 18 * * *"}'

# When using more than one trigger (CPU + Schedule as an example), the number of units will be determined by the highest value
`,
MinArgs: 0,
MaxArgs: 0,
}
Expand All @@ -54,6 +68,8 @@ func (c *AutoScaleSet) Flags() *gnuflag.FlagSet {
c.fs.Var((*int32Value)(&c.autoscale.MinUnits), "min", "Minimum Units")

c.fs.Var((*int32Value)(&c.autoscale.MaxUnits), "max", "Maximum Units")

c.fs.Var(&c.schedules, "schedule", "Schedule window to up/down scale. Example: {\"minReplicas\": 2, \"start\": \"0 6 * * *\", \"end\": \"0 18 * * *\"}")
}
return c.fs
}
Expand All @@ -70,6 +86,18 @@ func (c *AutoScaleSet) Run(ctx *cmd.Context, cli *cmd.Client) error {
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

_, err = apiClient.AppApi.AutoScaleAdd(context.TODO(), appName, c.autoscale)
if err != nil {
return err
Expand Down
Loading
Loading