Skip to content

Commit

Permalink
resources.requests support (#14) & fix rounding errors for statefulse…
Browse files Browse the repository at this point in the history
…t cpu resources
  • Loading branch information
druppelt committed Sep 11, 2024
1 parent 4f9a7c3 commit bddcf7a
Show file tree
Hide file tree
Showing 16 changed files with 216 additions and 118 deletions.
30 changes: 19 additions & 11 deletions cmd/kuota-calc.go
Original file line number Diff line number Diff line change
Expand Up @@ -121,18 +121,20 @@ func (opts *KuotaCalcOpts) run() error {
func (opts *KuotaCalcOpts) printDetailed(usage []*calc.ResourceUsage) {
w := tabwriter.NewWriter(opts.Out, 0, 0, 4, ' ', tabwriter.TabIndent)

fmt.Fprintf(w, "Version\tKind\tName\tReplicas\tStrategy\tMaxReplicas\tCPU\tMemory\t\n")
fmt.Fprintf(w, "Version\tKind\tName\tReplicas\tStrategy\tMaxReplicas\tCPURequest\tCPULimit\tMemoryRequest\tMemoryLimit\t\n")

for _, u := range usage {
fmt.Fprintf(w, "%s\t%s\t%s\t%d\t%s\t%d\t%s\t%s\t\n",
fmt.Fprintf(w, "%s\t%s\t%s\t%d\t%s\t%d\t%s\t%s\t%s\t%s\t\n",
u.Details.Version,
u.Details.Kind,
u.Details.Name,
u.Details.Replicas,
u.Details.Strategy,
u.Details.MaxReplicas,
u.CPU,
u.Memory,
u.CpuMin,
u.CpuMax,
u.MemoryMin,
u.MemoryMax,
)
}

Expand All @@ -145,17 +147,23 @@ func (opts *KuotaCalcOpts) printDetailed(usage []*calc.ResourceUsage) {

func (opts *KuotaCalcOpts) printSummary(usage []*calc.ResourceUsage) {
var (
cpuUsage resource.Quantity
memoryUsage resource.Quantity
cpuMinUsage resource.Quantity
cpuMaxUsage resource.Quantity
memoryMinUsage resource.Quantity
memoryMaxUsage resource.Quantity
)

for _, u := range usage {
cpuUsage.Add(*u.CPU)
memoryUsage.Add(*u.Memory)
cpuMinUsage.Add(*u.CpuMin)
cpuMaxUsage.Add(*u.CpuMax)
memoryMinUsage.Add(*u.MemoryMin)
memoryMaxUsage.Add(*u.MemoryMax)
}

fmt.Fprintf(opts.Out, "CPU: %s\nMemory: %s\n",
cpuUsage.String(),
memoryUsage.String(),
fmt.Fprintf(opts.Out, "CPU Request: %s\nCPU Limit: %s\nMemory Request: %s\nMemory Limit: %s\n",
cpuMinUsage.String(),
cpuMaxUsage.String(),
memoryMinUsage.String(),
memoryMaxUsage.String(),
)
}
2 changes: 1 addition & 1 deletion examples/deployment.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ spec:
template:
metadata:
labels:
app: myapp
app: myapp
spec:
containers:
- image: myapp:0.1.0
Expand Down
28 changes: 18 additions & 10 deletions internal/calc/calc.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,11 @@ func (cErr CalculationError) Unwrap() error {

// ResourceUsage summarizes the usage of compute resources for a k8s resource.
type ResourceUsage struct {
CPU *resource.Quantity
Memory *resource.Quantity
Details Details
CpuMin *resource.Quantity
CpuMax *resource.Quantity
MemoryMin *resource.Quantity
MemoryMax *resource.Quantity
Details Details
}

// Details contains a few details of a k8s resource, which are needed to generate a detailed resource
Expand All @@ -58,22 +60,28 @@ type Details struct {
MaxReplicas int32
}

func podResources(podSpec *v1.PodSpec) (cpu, memory *resource.Quantity) {
cpu = new(resource.Quantity)
memory = new(resource.Quantity)
func podResources(podSpec *v1.PodSpec) (cpuMin, cpuMax, memoryMin, memoryMax *resource.Quantity) {
cpuMin = new(resource.Quantity)
cpuMax = new(resource.Quantity)
memoryMin = new(resource.Quantity)
memoryMax = new(resource.Quantity)

for i := range podSpec.Containers {
container := podSpec.Containers[i]

cpu.Add(*container.Resources.Limits.Cpu())
memory.Add(*container.Resources.Limits.Memory())
cpuMin.Add(*container.Resources.Requests.Cpu())
cpuMax.Add(*container.Resources.Limits.Cpu())
memoryMin.Add(*container.Resources.Requests.Memory())
memoryMax.Add(*container.Resources.Limits.Memory())
}

for i := range podSpec.InitContainers {
container := podSpec.InitContainers[i]

cpu.Add(*container.Resources.Limits.Cpu())
memory.Add(*container.Resources.Limits.Memory())
cpuMin.Add(*container.Resources.Requests.Cpu())
cpuMax.Add(*container.Resources.Limits.Cpu())
memoryMin.Add(*container.Resources.Requests.Memory())
memoryMax.Add(*container.Resources.Limits.Memory())
}

return
Expand Down
16 changes: 11 additions & 5 deletions internal/calc/calc_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package calc

import (
"errors"
"k8s.io/apimachinery/pkg/api/resource"
"testing"

"github.com/stretchr/testify/require"
Expand All @@ -19,7 +20,6 @@ spec:
kind: Service
name: coffee-svc `


var normalDeployment = `---
apiVersion: apps/v1
kind: Deployment
Expand Down Expand Up @@ -96,6 +96,9 @@ spec:
- image: myinit:v1.0.7
name: myinit
resources:
requests:
cpu: '50m'
memory: '100Mi'
limits:
cpu: '100m'
memory: '200Mi'
Expand Down Expand Up @@ -454,7 +457,7 @@ spec:
restartPolicy: Never
backoffLimit: 4`

var normalCronJob =`---
var normalCronJob = `---
apiVersion: batch/v1
kind: CronJob
metadata:
Expand Down Expand Up @@ -500,7 +503,7 @@ spec:
memory: 2Gi
terminationGracePeriodSeconds: 30`

var normalDaemonSet = `
var normalDaemonSet = `
apiVersion: apps/v1
kind: DaemonSet
metadata:
Expand Down Expand Up @@ -544,8 +547,11 @@ func TestResourceQuotaFromYaml(t *testing.T) {
usage, err = ResourceQuotaFromYaml([]byte(unsupportedOpenshiftRoute))
t.Log(err)
r.Error(err)
r.True(errors.Is(err,ErrResourceNotSupported))
r.True(errors.Is(err, ErrResourceNotSupported))
r.Nil(usage)
r.True(errors.As(err,&calcErr))
r.True(errors.As(err, &calcErr))
}

func AssertEqualQuantities(r *require.Assertions, expected resource.Quantity, actual resource.Quantity, name string) {
r.Conditionf(func() bool { return expected.Equal(actual) }, name+" expected: "+expected.String()+" but was: "+actual.String())
}
8 changes: 5 additions & 3 deletions internal/calc/cronjob.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,13 @@ package calc
import batchV1 "k8s.io/api/batch/v1"

func cronjob(cronjob batchV1.CronJob) *ResourceUsage {
cpu, memory := podResources(&cronjob.Spec.JobTemplate.Spec.Template.Spec)
cpuMin, cpuMax, memoryMin, memoryMax := podResources(&cronjob.Spec.JobTemplate.Spec.Template.Spec)

resourceUsage := ResourceUsage{
CPU: cpu,
Memory: memory,
CpuMin: cpuMin,
CpuMax: cpuMax,
MemoryMin: memoryMin,
MemoryMax: memoryMax,
Details: Details{
Version: cronjob.APIVersion,
Kind: cronjob.Kind,
Expand Down
26 changes: 16 additions & 10 deletions internal/calc/cronjob_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,19 +9,23 @@ import (

func TestCronJob(t *testing.T) {
var tests = []struct {
name string
cronjob string
cpu resource.Quantity
memory resource.Quantity
name string
cronjob string
cpuMin resource.Quantity
cpuMax resource.Quantity
memoryMin resource.Quantity
memoryMax resource.Quantity
replicas int32
maxReplicas int32
strategy string
}{
{
name: "ok",
cronjob: normalCronJob,
cpu: resource.MustParse("1"),
memory: resource.MustParse("4Gi"),
name: "ok",
cronjob: normalCronJob,
cpuMin: resource.MustParse("250m"),
cpuMax: resource.MustParse("1"),
memoryMin: resource.MustParse("2Gi"),
memoryMax: resource.MustParse("4Gi"),
},
}

Expand All @@ -34,8 +38,10 @@ func TestCronJob(t *testing.T) {
r.NoError(err)
r.NotEmpty(usage)

r.Equalf(test.cpu.Value(), usage.CPU.Value(), "cpu value")
r.Equalf(test.memory.Value(), usage.Memory.Value(), "memory value")
AssertEqualQuantities(r, test.cpuMin, *usage.CpuMin, "cpu request value")
AssertEqualQuantities(r, test.cpuMax, *usage.CpuMax, "cpu limit value")
AssertEqualQuantities(r, test.memoryMin, *usage.MemoryMin, "memory request value")
AssertEqualQuantities(r, test.memoryMax, *usage.MemoryMax, "memory limit value")
r.Equalf(test.replicas, usage.Details.Replicas, "replicas")
r.Equalf(test.maxReplicas, usage.Details.MaxReplicas, "maxReplicas")
r.Equalf(string(test.strategy), usage.Details.Strategy, "strategy")
Expand Down
8 changes: 5 additions & 3 deletions internal/calc/daemonset.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,13 @@ import (
)

func daemonSet(dSet appsv1.DaemonSet) *ResourceUsage {
cpu, memory := podResources(&dSet.Spec.Template.Spec)
cpuMin, cpuMax, memoryMin, memoryMax := podResources(&dSet.Spec.Template.Spec)

resourceUsage := ResourceUsage{
CPU: cpu,
Memory: memory,
CpuMin: cpuMin,
CpuMax: cpuMax,
MemoryMin: memoryMin,
MemoryMax: memoryMax,
Details: Details{
Version: dSet.APIVersion,
Kind: dSet.Kind,
Expand Down
28 changes: 17 additions & 11 deletions internal/calc/daemonset_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,21 +10,25 @@ import (

func TestDaemonSet(t *testing.T) {
var tests = []struct {
name string
daemonset string
cpu resource.Quantity
memory resource.Quantity
name string
daemonset string
cpuMin resource.Quantity
cpuMax resource.Quantity
memoryMin resource.Quantity
memoryMax resource.Quantity
replicas int32
maxReplicas int32
strategy appsv1.StatefulSetUpdateStrategyType
}{
{
name: "ok",
daemonset: normalDaemonSet,
replicas: 1,
name: "ok",
daemonset: normalDaemonSet,
replicas: 1,
maxReplicas: 1,
cpu: resource.MustParse("2"),
memory: resource.MustParse("2Gi"),
cpuMin: resource.MustParse("500m"),
cpuMax: resource.MustParse("2"),
memoryMin: resource.MustParse("200Mi"),
memoryMax: resource.MustParse("2Gi"),
},
}

Expand All @@ -37,8 +41,10 @@ func TestDaemonSet(t *testing.T) {
r.NoError(err)
r.NotEmpty(usage)

r.Equalf(test.cpu.Value(), usage.CPU.Value(), "cpu value")
r.Equalf(test.memory.Value(), usage.Memory.Value(), "memory value")
AssertEqualQuantities(r, test.cpuMin, *usage.CpuMin, "cpu request value")
AssertEqualQuantities(r, test.cpuMax, *usage.CpuMax, "cpu limit value")
AssertEqualQuantities(r, test.memoryMin, *usage.MemoryMin, "memory request value")
AssertEqualQuantities(r, test.memoryMax, *usage.MemoryMax, "memory limit value")
r.Equalf(test.replicas, usage.Details.Replicas, "replicas")
r.Equalf(test.maxReplicas, usage.Details.MaxReplicas, "maxReplicas")
r.Equalf(string(test.strategy), usage.Details.Strategy, "strategy")
Expand Down
25 changes: 17 additions & 8 deletions internal/calc/deployment.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,10 @@ func deployment(deployment appsv1.Deployment) (*ResourceUsage, error) {

if *replicas == 0 {
return &ResourceUsage{
CPU: new(resource.Quantity),
Memory: new(resource.Quantity),
CpuMin: new(resource.Quantity),
CpuMax: new(resource.Quantity),
MemoryMin: new(resource.Quantity),
MemoryMax: new(resource.Quantity),
Details: Details{
Version: deployment.APIVersion,
Kind: deployment.Kind,
Expand Down Expand Up @@ -90,16 +92,23 @@ func deployment(deployment appsv1.Deployment) (*ResourceUsage, error) {
return nil, fmt.Errorf("deployment: %s deployment strategy %q is unknown", deployment.Name, strategy.Type)
}

cpu, memory := podResources(&deployment.Spec.Template.Spec)
cpuMin, cpuMax, memoryMin, memoryMax := podResources(&deployment.Spec.Template.Spec)

mem := float64(memory.Value()) * float64(*replicas) * resourceOverhead
memory.Set(int64(math.Round(mem)))
memMin := float64(memoryMin.Value()) * float64(*replicas) * resourceOverhead
memoryMin.Set(int64(math.Round(memMin)))

cpu.SetMilli(int64(math.Round(float64(cpu.MilliValue()) * float64(*replicas) * resourceOverhead)))
memMax := float64(memoryMax.Value()) * float64(*replicas) * resourceOverhead
memoryMax.Set(int64(math.Round(memMax)))

cpuMin.SetMilli(int64(math.Round(float64(cpuMin.MilliValue()) * float64(*replicas) * resourceOverhead)))

cpuMax.SetMilli(int64(math.Round(float64(cpuMax.MilliValue()) * float64(*replicas) * resourceOverhead)))

resourceUsage := ResourceUsage{
CPU: cpu,
Memory: memory,
CpuMin: cpuMin,
CpuMax: cpuMax,
MemoryMin: memoryMin,
MemoryMax: memoryMax,
Details: Details{
Version: deployment.APIVersion,
Kind: deployment.Kind,
Expand Down
Loading

0 comments on commit bddcf7a

Please sign in to comment.