// 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()) } }