Files
Oliver BählerandGitHub 65a4e19e02 feat: requests and limit policies (#2095)
* chore: save progress

Signed-off-by: Oliver Baehler <oliver@sudo-i.net>

* feat: requests and limit policies

Signed-off-by: Oliver Baehler <oliver@sudo-i.net>

* feat: requests and limit policies

Signed-off-by: Oliver Baehler <oliver@sudo-i.net>

* feat: requests and limit policies

Signed-off-by: Oliver Baehler <oliver@sudo-i.net>

* feat: requests and limit policies

Signed-off-by: Oliver Baehler <oliver@sudo-i.net>

* feat: requests and limit policies

Signed-off-by: Oliver Baehler <oliver@sudo-i.net>

* feat: requests and limit policies

Signed-off-by: Oliver Baehler <oliver@sudo-i.net>

* feat: requests and limit policies

Signed-off-by: Oliver Baehler <oliver@sudo-i.net>

* feat: requests and limit policies

Signed-off-by: Oliver Baehler <oliver@sudo-i.net>

* feat: requests and limit policies

Signed-off-by: Oliver Baehler <oliver@sudo-i.net>

---------

Signed-off-by: Oliver Baehler <oliver@sudo-i.net>
2026-08-21 14:31:06 +02:00

304 lines
9.5 KiB
Go

// Copyright 2020-2026 Project Capsule Authors
// SPDX-License-Identifier: Apache-2.0
package mutation
import (
"testing"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/resource"
"github.com/projectcapsule/capsule/pkg/api/rules"
)
func TestMutatePodResourcesUsesAllDefaultTargets(t *testing.T) {
t.Parallel()
pod := &corev1.Pod{Spec: corev1.PodSpec{
Containers: []corev1.Container{{
Name: "app",
Resources: corev1.ResourceRequirements{
Requests: corev1.ResourceList{corev1.ResourceMemory: resource.MustParse("1Gi")},
Limits: corev1.ResourceList{corev1.ResourceCPU: resource.MustParse("1")},
},
}},
InitContainers: []corev1.Container{{
Name: "init",
Resources: corev1.ResourceRequirements{
Requests: corev1.ResourceList{corev1.ResourceMemory: resource.MustParse("512Mi")},
},
}},
Resources: &corev1.ResourceRequirements{
Requests: corev1.ResourceList{corev1.ResourceCPU: resource.MustParse("500m")},
Limits: corev1.ResourceList{corev1.ResourceCPU: resource.MustParse("1")},
},
}}
bodies := []*rules.NamespaceRuleBodyNamespace{{Enforce: &rules.NamespaceRuleEnforceBody{
Workloads: rules.NamespaceRuleEnforceWorkloadsBody{
Resources: &rules.WorkloadResourceRules{
Requests: map[corev1.ResourceName]rules.WorkloadResourceRequestPolicy{
corev1.ResourceCPU: {
Policy: rules.WorkloadResourceRequestPolicyDefault,
Value: quantityPointer("100m"),
},
},
Limits: map[corev1.ResourceName]rules.WorkloadResourceLimitPolicy{
corev1.ResourceCPU: {Policy: rules.WorkloadResourceLimitPolicyRemove},
corev1.ResourceMemory: {
Policy: rules.WorkloadResourceLimitPolicyMatchRequest,
},
},
},
},
}}}
changed, err := MutatePodResources(pod, bodies)
if err != nil {
t.Fatalf("MutatePodResources() error = %v", err)
}
if !changed {
t.Fatal("MutatePodResources() changed = false, want true")
}
assertResourceQuantity(t, pod.Spec.Containers[0].Resources.Requests, corev1.ResourceCPU, "100m")
assertResourceMissing(t, pod.Spec.Containers[0].Resources.Limits, corev1.ResourceCPU)
assertResourceQuantity(t, pod.Spec.Containers[0].Resources.Limits, corev1.ResourceMemory, "1Gi")
assertResourceQuantity(t, pod.Spec.InitContainers[0].Resources.Requests, corev1.ResourceCPU, "100m")
assertResourceQuantity(t, pod.Spec.InitContainers[0].Resources.Limits, corev1.ResourceMemory, "512Mi")
assertResourceQuantity(t, pod.Spec.Resources.Requests, corev1.ResourceCPU, "500m")
assertResourceQuantity(t, pod.Spec.Resources.Requests, corev1.ResourceMemory, "1Gi")
assertResourceMissing(t, pod.Spec.Resources.Limits, corev1.ResourceCPU)
}
func TestMutatePodResourcesBackfillsOnlySupportedMissingPodRequests(t *testing.T) {
t.Parallel()
pod := &corev1.Pod{Spec: corev1.PodSpec{
Containers: []corev1.Container{{
Name: "app",
Resources: corev1.ResourceRequirements{Requests: corev1.ResourceList{
corev1.ResourceMemory: resource.MustParse("1Gi"),
corev1.ResourceEphemeralStorage: resource.MustParse("2Gi"),
}},
}, {
Name: "sidecar",
Resources: corev1.ResourceRequirements{Requests: corev1.ResourceList{
corev1.ResourceMemory: resource.MustParse("512Mi"),
}},
}},
Resources: &corev1.ResourceRequirements{
Requests: corev1.ResourceList{corev1.ResourceCPU: resource.MustParse("500m")},
},
}}
bodies := []*rules.NamespaceRuleBodyNamespace{{Enforce: &rules.NamespaceRuleEnforceBody{
Workloads: rules.NamespaceRuleEnforceWorkloadsBody{
Targets: []rules.WorkloadValidationTarget{rules.ValidateContainers},
Resources: &rules.WorkloadResourceRules{Requests: map[corev1.ResourceName]rules.WorkloadResourceRequestPolicy{
corev1.ResourceCPU: {
Policy: rules.WorkloadResourceRequestPolicyDefault,
Value: quantityPointer("100m"),
},
}},
},
}}}
changed, err := MutatePodResources(pod, bodies)
if err != nil {
t.Fatalf("MutatePodResources() error = %v", err)
}
if !changed {
t.Fatal("MutatePodResources() changed = false, want true")
}
assertResourceQuantity(t, pod.Spec.Resources.Requests, corev1.ResourceCPU, "500m")
assertResourceQuantity(t, pod.Spec.Resources.Requests, corev1.ResourceMemory, "1536Mi")
assertResourceMissing(t, pod.Spec.Resources.Requests, corev1.ResourceEphemeralStorage)
}
func TestMutatePodResourcesDefaultTargetsSkipPodIncompatibleResources(t *testing.T) {
t.Parallel()
pod := &corev1.Pod{Spec: corev1.PodSpec{
Containers: []corev1.Container{{Name: "app"}},
}}
bodies := []*rules.NamespaceRuleBodyNamespace{{Enforce: &rules.NamespaceRuleEnforceBody{
Workloads: rules.NamespaceRuleEnforceWorkloadsBody{
Resources: &rules.WorkloadResourceRules{
Requests: map[corev1.ResourceName]rules.WorkloadResourceRequestPolicy{
corev1.ResourceEphemeralStorage: {
Policy: rules.WorkloadResourceRequestPolicyDefault,
Value: quantityPointer("1Gi"),
},
},
},
},
}}}
changed, err := MutatePodResources(pod, bodies)
if err != nil {
t.Fatalf("MutatePodResources() error = %v", err)
}
if !changed {
t.Fatal("MutatePodResources() changed = false, want true")
}
assertResourceQuantity(
t,
pod.Spec.Containers[0].Resources.Requests,
corev1.ResourceEphemeralStorage,
"1Gi",
)
if pod.Spec.Resources != nil {
t.Fatalf("pod.Spec.Resources = %#v, want nil", pod.Spec.Resources)
}
}
func TestMutatePodResourcesPodRatio(t *testing.T) {
t.Parallel()
pod := &corev1.Pod{Spec: corev1.PodSpec{
Resources: &corev1.ResourceRequirements{
Requests: corev1.ResourceList{corev1.ResourceCPU: resource.MustParse("500m")},
},
}}
bodies := []*rules.NamespaceRuleBodyNamespace{{Enforce: &rules.NamespaceRuleEnforceBody{
Workloads: rules.NamespaceRuleEnforceWorkloadsBody{
Targets: []rules.WorkloadValidationTarget{rules.ValidatePod},
Resources: &rules.WorkloadResourceRules{
Limits: map[corev1.ResourceName]rules.WorkloadResourceLimitPolicy{
corev1.ResourceCPU: {
Policy: rules.WorkloadResourceLimitPolicyRatio,
Value: quantityPointer("1.5"),
},
},
},
},
}}}
changed, err := MutatePodResources(pod, bodies)
if err != nil {
t.Fatalf("MutatePodResources() error = %v", err)
}
if !changed {
t.Fatal("MutatePodResources() changed = false, want true")
}
assertResourceQuantity(t, pod.Spec.Resources.Limits, corev1.ResourceCPU, "750m")
}
func TestMutatePodResourcesPreservesExplicitRatioLimit(t *testing.T) {
t.Parallel()
pod := &corev1.Pod{Spec: corev1.PodSpec{Containers: []corev1.Container{{
Name: "app",
Resources: corev1.ResourceRequirements{
Requests: corev1.ResourceList{corev1.ResourceMemory: resource.MustParse("1Gi")},
Limits: corev1.ResourceList{corev1.ResourceMemory: resource.MustParse("2Gi")},
},
}}}}
bodies := resourceMutationBodies(
rules.WorkloadResourceLimitPolicyRatio,
quantityPointer("1.5"),
)
changed, err := MutatePodResources(pod, bodies)
if err != nil {
t.Fatalf("MutatePodResources() error = %v", err)
}
if changed {
t.Fatal("MutatePodResources() changed an explicitly supplied ratio limit")
}
assertResourceQuantity(t, pod.Spec.Containers[0].Resources.Limits, corev1.ResourceMemory, "2Gi")
}
func TestMutatePodResourcesLastPolicyWins(t *testing.T) {
t.Parallel()
pod := &corev1.Pod{Spec: corev1.PodSpec{Containers: []corev1.Container{{
Name: "app",
Resources: corev1.ResourceRequirements{
Limits: corev1.ResourceList{corev1.ResourceCPU: resource.MustParse("1")},
},
}}}}
bodies := []*rules.NamespaceRuleBodyNamespace{
{Enforce: &rules.NamespaceRuleEnforceBody{Workloads: rules.NamespaceRuleEnforceWorkloadsBody{
Resources: &rules.WorkloadResourceRules{Limits: map[corev1.ResourceName]rules.WorkloadResourceLimitPolicy{
corev1.ResourceCPU: {Policy: rules.WorkloadResourceLimitPolicyRemove},
}},
}}},
{Enforce: &rules.NamespaceRuleEnforceBody{Workloads: rules.NamespaceRuleEnforceWorkloadsBody{
Targets: []rules.WorkloadValidationTarget{rules.ValidateContainers},
Resources: &rules.WorkloadResourceRules{Limits: map[corev1.ResourceName]rules.WorkloadResourceLimitPolicy{
corev1.ResourceCPU: {Policy: rules.WorkloadResourceLimitPolicyPreserve},
}},
}}},
}
changed, err := MutatePodResources(pod, bodies)
if err != nil {
t.Fatalf("MutatePodResources() error = %v", err)
}
if changed {
t.Fatal("MutatePodResources() ignored the later Preserve policy")
}
assertResourceQuantity(t, pod.Spec.Containers[0].Resources.Limits, corev1.ResourceCPU, "1")
}
func resourceMutationBodies(
policy rules.WorkloadResourceLimitPolicyType,
value *resource.Quantity,
) []*rules.NamespaceRuleBodyNamespace {
return []*rules.NamespaceRuleBodyNamespace{{Enforce: &rules.NamespaceRuleEnforceBody{
Workloads: rules.NamespaceRuleEnforceWorkloadsBody{
Resources: &rules.WorkloadResourceRules{
Limits: map[corev1.ResourceName]rules.WorkloadResourceLimitPolicy{
corev1.ResourceMemory: {Policy: policy, Value: value},
},
},
},
}}}
}
func quantityPointer(value string) *resource.Quantity {
quantity := resource.MustParse(value)
return &quantity
}
func assertResourceQuantity(
t *testing.T,
resources corev1.ResourceList,
name corev1.ResourceName,
want string,
) {
t.Helper()
got, found := resources[name]
if !found {
t.Fatalf("resource %q is missing", name)
}
wantQuantity := resource.MustParse(want)
if got.Cmp(wantQuantity) != 0 {
t.Fatalf("resource %q = %s, want %s", name, got.String(), wantQuantity.String())
}
}
func assertResourceMissing(t *testing.T, resources corev1.ResourceList, name corev1.ResourceName) {
t.Helper()
if value, found := resources[name]; found {
t.Fatalf("resource %q = %s, want missing", name, value.String())
}
}