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

plan: Add support to display CPU Burst #214

Merged
merged 6 commits into from
Oct 17, 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
4 changes: 2 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,9 @@ 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-20231005173907-4607cc1f111e
github.com/tsuru/go-tsuruclient v0.0.0-20231009130311-a01dfd615e16
github.com/tsuru/tablecli v0.0.0-20190131152944-7ded8a3383c6
github.com/tsuru/tsuru v0.0.0-20231003184130-e29d84f36397
github.com/tsuru/tsuru v0.0.0-20231009130140-65592312e508
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c
gopkg.in/yaml.v2 v2.4.0
k8s.io/apimachinery v0.23.17
Expand Down
10 changes: 4 additions & 6 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -733,14 +733,12 @@ 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-20231004185254-b386081b2ca8 h1:ne140iJLe0drfwdz4cvTrTf/rmyirD8VI+Ivm+l6XxU=
github.com/tsuru/go-tsuruclient v0.0.0-20231004185254-b386081b2ca8/go.mod h1:BmePxHey9hxrxk0kzTMHFFr7aJWXSxtlrUx6FIeV0Ic=
github.com/tsuru/go-tsuruclient v0.0.0-20231005173907-4607cc1f111e h1:SyzgPCFdzo4u8BKIXKGm5+wqSSX5kttnsjMPEzThl3I=
github.com/tsuru/go-tsuruclient v0.0.0-20231005173907-4607cc1f111e/go.mod h1:BmePxHey9hxrxk0kzTMHFFr7aJWXSxtlrUx6FIeV0Ic=
github.com/tsuru/go-tsuruclient v0.0.0-20231009130311-a01dfd615e16 h1:gjwhjJTOuPlHhytkBXvfEzIzyYytePVvGeq7REbeBGY=
github.com/tsuru/go-tsuruclient v0.0.0-20231009130311-a01dfd615e16/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-20231003184130-e29d84f36397 h1:9NpNEFIUgmEPI4H+ngFQGisRrpHv2UWKoiPApXUaCfI=
github.com/tsuru/tsuru v0.0.0-20231003184130-e29d84f36397/go.mod h1:is8CUBIZaH1mwyvvL3br5Bts/a29iTAQqEQbV7jSOJQ=
github.com/tsuru/tsuru v0.0.0-20231009130140-65592312e508 h1:eaMg/uBeTv6B7O+AMTq3OKqNsiA0/kB5Zkgy7ipXgcI=
github.com/tsuru/tsuru v0.0.0-20231009130140-65592312e508/go.mod h1:is8CUBIZaH1mwyvvL3br5Bts/a29iTAQqEQbV7jSOJQ=
github.com/tv42/httpunix v0.0.0-20191220191345-2ba4b9c3382c/go.mod h1:hzIxponao9Kjc7aWznkXaL4U4TWaDSs8zcsY4Ka08nM=
github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc=
github.com/urfave/cli v0.0.0-20171014202726-7bc6a0acffa5/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
Expand Down
22 changes: 19 additions & 3 deletions tsuru/client/apps.go
Original file line number Diff line number Diff line change
Expand Up @@ -219,13 +219,13 @@ type AppUpdate struct {
cmd.AppNameMixIn
cmd.ConfirmationCommand

memory, cpu string
memory, cpu, cpuBurst string
}

func (c *AppUpdate) Info() *cmd.Info {
return &cmd.Info{
Name: "app-update",
Usage: "app update [-a/--app appname] [--description/-d description] [--plan/-p plan name] [--pool/-o pool] [--team-owner/-t team owner] [--platform/-l platform] [-i/--image-reset] [--cpu cpu] [--memory memory] [--tag/-g tag]...",
Usage: "app update [-a/--app appname] [--description/-d description] [--plan/-p plan name] [--pool/-o pool] [--team-owner/-t team owner] [--platform/-l platform] [-i/--image-reset] [--cpu cpu] [--memory memory] [--cpu-burst-factor cpu-burst-factor] [--tag/-g tag]...",
Desc: `Updates an app, changing its description, tags, plan or pool information.`,
}
}
Expand Down Expand Up @@ -257,6 +257,8 @@ func (c *AppUpdate) Flags() *gnuflag.FlagSet {
flagSet.Var((*cmd.StringSliceFlag)(&c.args.Tags), "g", tagMessage)
flagSet.Var((*cmd.StringSliceFlag)(&c.args.Tags), "tag", tagMessage)
flagSet.StringVar(&c.cpu, "cpu", "", "CPU limit for app, this will override the plan cpu value. One cpu is equivalent to 1 vCPU/Core, fractional requests are allowed and the expression 0.1 is equivalent to the expression 100m")
flagSet.StringVar(&c.cpuBurst, "cpu-burst-factor", "", "The multiplier to determine the limits of the CPU burst. Setting 1 disables burst")

flagSet.StringVar(&c.memory, "memory", "", "Memory limit for app, this will override the plan memory value. You can express memory as a bytes integer or using one of these suffixes: E, P, T, G, M, K, Ei, Pi, Ti, Gi, Mi, Ki")
c.fs = cmd.MergeFlagSet(
c.AppNameMixIn.Flags(),
Expand Down Expand Up @@ -296,6 +298,20 @@ func (c *AppUpdate) Run(ctx *cmd.Context, cli *cmd.Client) error {
c.args.Planoverride.Memory = &val
}

if c.cpuBurst != "" {
var cpuBurst float64
cpuBurst, err = strconv.ParseFloat(c.cpuBurst, 64)
if err != nil {
return err
}

if cpuBurst < 1 {
return errors.New("Invalid factor, please use a value greater equal 1")
}

c.args.Planoverride.CpuBurst = &cpuBurst
}

appName := c.Flags().Lookup("app").Value.String()
if appName == "" {
return errors.New("Please use the -a/--app flag to specify which app you want to update.")
Expand Down Expand Up @@ -748,7 +764,7 @@ func (a *app) String(simplified bool) string {
if !simplified && (a.Plan.Memory != 0 || a.Plan.CPUMilli != 0) {
buf.WriteString("\n")
buf.WriteString("App Plan:\n")
buf.WriteString(renderPlans([]apptypes.Plan{a.Plan}, false, false))
buf.WriteString(renderPlans([]apptypes.Plan{a.Plan}, renderPlansOpts{}))
}
if !simplified && internalAddressesTable.Rows() > 0 {
buf.WriteString("\n")
Expand Down
67 changes: 67 additions & 0 deletions tsuru/client/apps_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -613,6 +613,73 @@ func (s *S) TestAppUpdateWithCPUAndMemory(c *check.C) {
c.Assert(stdout.String(), check.Equals, expected)
}

func (s *S) TestAppUpdateWithCPUBurst(c *check.C) {
wpjunior marked this conversation as resolved.
Show resolved Hide resolved
var stdout, stderr bytes.Buffer
expected := fmt.Sprintf("App %q has been updated!\n", "ble")
context := cmd.Context{
Stdout: &stdout,
Stderr: &stderr,
}
trans := &cmdtest.ConditionalTransport{
Transport: cmdtest.Transport{Status: http.StatusOK},
CondFunc: func(req *http.Request) bool {
url := strings.HasSuffix(req.URL.Path, "/apps/ble")
method := req.Method == "PUT"
data, err := io.ReadAll(req.Body)
c.Assert(err, check.IsNil)
var result map[string]interface{}
err = json.Unmarshal(data, &result)
c.Assert(err, check.IsNil)
c.Assert(result, check.DeepEquals, map[string]interface{}{
"planoverride": map[string]interface{}{
"cpuBurst": float64(1.3),
},
"metadata": map[string]interface{}{},
})
return url && method
},
}
client := cmd.NewClient(&http.Client{Transport: trans}, nil, manager)
command := AppUpdate{}
command.Flags().Parse(true, []string{"-a", "ble", "--cpu-burst-factor", "1.3"})
err := command.Run(&context, client)
c.Assert(err, check.IsNil)
c.Assert(stdout.String(), check.Equals, expected)
}

func (s *S) TestAppUpdateWithInvalidCPUBurst(c *check.C) {
var stdout, stderr bytes.Buffer
context := cmd.Context{
Stdout: &stdout,
Stderr: &stderr,
}
trans := &cmdtest.ConditionalTransport{
Transport: cmdtest.Transport{Status: http.StatusOK},
CondFunc: func(req *http.Request) bool {
url := strings.HasSuffix(req.URL.Path, "/apps/ble")
method := req.Method == "PUT"
data, err := io.ReadAll(req.Body)
c.Assert(err, check.IsNil)
var result map[string]interface{}
err = json.Unmarshal(data, &result)
c.Assert(err, check.IsNil)
c.Assert(result, check.DeepEquals, map[string]interface{}{
"planoverride": map[string]interface{}{
"cpuBurst": float64(1.3),
},
"metadata": map[string]interface{}{},
})
return url && method
},
}
client := cmd.NewClient(&http.Client{Transport: trans}, nil, manager)
command := AppUpdate{}
command.Flags().Parse(true, []string{"-a", "ble", "--cpu-burst-factor", "0.5"})
err := command.Run(&context, client)
c.Assert(err, check.NotNil)
c.Assert(err.Error(), check.Equals, "Invalid factor, please use a value greater equal 1")
}

func (s *S) TestAppUpdateWithoutArgs(c *check.C) {
var stdout, stderr bytes.Buffer
expected := "Please use the -a/--app flag to specify which app you want to update."
Expand Down
142 changes: 134 additions & 8 deletions tsuru/client/plan.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,21 @@ import (
)

type PlanList struct {
bytes bool
fs *gnuflag.FlagSet
bytes bool
k8sFriendly bool
showMaxBurstAllowed bool

fs *gnuflag.FlagSet
}

func (c *PlanList) Flags() *gnuflag.FlagSet {
if c.fs == nil {
c.fs = gnuflag.NewFlagSet("plan-list", gnuflag.ExitOnError)
bytes := "bytesized units for memory and swap."
c.fs.BoolVar(&c.bytes, "bytes", false, bytes)
c.fs.BoolVar(&c.showMaxBurstAllowed, "show-max-cpu-burst-allowed", false, "show column about max CPU burst allowed by plan")
c.fs.BoolVar(&c.k8sFriendly, "kubernetes-friendly", false, "show values friendly for a kubernetes user")

c.fs.BoolVar(&c.bytes, "b", false, bytes)
}
return c.fs
Expand All @@ -35,23 +41,44 @@ func (c *PlanList) Flags() *gnuflag.FlagSet {
func (c *PlanList) Info() *cmd.Info {
return &cmd.Info{
Name: "plan-list",
Usage: "plan list [--bytes]",
Usage: "plan list [--bytes][--kubernetes-friendly][--show-max-cpu-burst-allowed]",
Desc: "List available plans that can be used when creating an app.",
MinArgs: 0,
}
}

func renderPlans(plans []apptypes.Plan, isBytes, showDefaultColumn bool) string {
type renderPlansOpts struct {
isBytes, showDefaultColumn, showMaxBurstAllowed bool
}

func renderPlans(plans []apptypes.Plan, opts renderPlansOpts) string {
table := tablecli.NewTable()
table.Headers = []string{"Name", "CPU", "Memory"}

if showDefaultColumn {
showBurstColumn := false

for _, p := range plans {
if hasBurst(p) {
showBurstColumn = true
break
}
}

if showBurstColumn {
table.Headers = append(table.Headers, "CPU Burst (default)")
}

if showBurstColumn && opts.showMaxBurstAllowed {
table.Headers = append(table.Headers, "CPU Burst (max customizable)")
}

if opts.showDefaultColumn {
table.Headers = append(table.Headers, "Default")
}

for _, p := range plans {
var cpu, memory string
if isBytes {
if opts.isBytes {
memory = fmt.Sprintf("%d", p.Memory)
} else {
memory = resource.NewQuantity(p.Memory, resource.BinarySI).String()
Expand All @@ -73,14 +100,107 @@ func renderPlans(plans []apptypes.Plan, isBytes, showDefaultColumn bool) string
memory,
}

if showDefaultColumn {
if showBurstColumn {
cpuBurst := p.CPUBurst.Default
cpuBurstObservation := ""
if p.Override.CPUBurst != nil {
cpuBurst = *p.Override.CPUBurst
cpuBurstObservation = " (override)"
}

row = append(row, displayCPUBurst(p.CPUMilli, cpuBurst)+cpuBurstObservation)
}

if showBurstColumn && opts.showMaxBurstAllowed {
row = append(row, displayCPUBurst(p.CPUMilli, p.CPUBurst.MaxAllowed))
}

if opts.showDefaultColumn {
row = append(row, strconv.FormatBool(p.Default))
}
table.AddRow(row)
}
return table.String()
}

func renderPlansK8SFriendly(plans []apptypes.Plan, showMaxBurstAllowed bool) string {
table := tablecli.NewTable()
table.Headers = []string{"Name"}

showCPULimitsColumn := false
for _, p := range plans {
if hasBurst(p) {
showCPULimitsColumn = true
break
}
}

if showCPULimitsColumn {
table.Headers = append(table.Headers, "CPU requests", "CPU limits")
} else {
table.Headers = append(table.Headers, "CPU requests/limits")
}

if showMaxBurstAllowed {
table.Headers = append(table.Headers, "CPU limits (max customizable)")
}

table.Headers = append(table.Headers, "Memory requests/limits", "Default")

for _, p := range plans {
memory := resource.NewQuantity(p.Memory, resource.BinarySI).String()
cpuRequest := resource.NewMilliQuantity(int64(p.CPUMilli), resource.DecimalSI).String()
maxCPULimit := resource.NewMilliQuantity(int64(float64(p.CPUMilli)*p.CPUBurst.MaxAllowed), resource.DecimalSI).String()

row := []string{
p.Name,
}

if showCPULimitsColumn {
cpuBurst := p.CPUBurst.Default
if cpuBurst < 1 {
cpuBurst = 1
}
defaultCPULimit := resource.NewMilliQuantity(int64(float64(p.CPUMilli)*cpuBurst), resource.DecimalSI).String()
row = append(row, cpuRequest, defaultCPULimit)
} else {
row = append(row, cpuRequest)
}

if showMaxBurstAllowed {
row = append(row, maxCPULimit)
}

row = append(row, memory, strconv.FormatBool(p.Default))

table.AddRow(row)
}
return table.String()
}

func hasBurst(p apptypes.Plan) bool {
if p.CPUMilli == 0 {
return false
}
if p.CPUBurst.Default != 0 {
return true
}

if p.Override.CPUBurst != nil {
return true
}
return false
}

func displayCPUBurst(currentCPU int, burst float64) string {
if currentCPU == 0 || burst < 1 {
return ""
}

cpu := int(float64(currentCPU) * burst / 10)
return fmt.Sprintf("up to %g", float64(cpu)) + "%"
}

func (c *PlanList) Run(context *cmd.Context, client *cmd.Client) error {
url, err := cmd.GetURL("/plans")
if err != nil {
Expand All @@ -104,6 +224,12 @@ func (c *PlanList) Run(context *cmd.Context, client *cmd.Client) error {
if err != nil {
return err
}
fmt.Fprintf(context.Stdout, "%s", renderPlans(plans, c.bytes, true))

if c.k8sFriendly {
fmt.Fprintf(context.Stdout, "%s", renderPlansK8SFriendly(plans, c.showMaxBurstAllowed))
} else {
fmt.Fprintf(context.Stdout, "%s", renderPlans(plans, renderPlansOpts{isBytes: c.bytes, showDefaultColumn: true, showMaxBurstAllowed: c.showMaxBurstAllowed}))
}

return nil
}
Loading
Loading