pkg/audit/pod_resources_test.go
Rama main · réplica de lectura del repositorio original.
package audit
import (
"reflect"
"slices"
"testing"
appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
func resourceBudgets(cpu, memory string) corev1.ResourceList {
values := corev1.ResourceList{}
if cpu != "" {
values[corev1.ResourceCPU] = resource.MustParse(cpu)
}
if memory != "" {
values[corev1.ResourceMemory] = resource.MustParse(memory)
}
return values
}
func resourceTemplateInput(spec corev1.PodSpec, version string, lrs ...*corev1.LimitRange) *CheckInput {
if lrs == nil {
lrs = []*corev1.LimitRange{}
}
return &CheckInput{
Deployments: []*appsv1.Deployment{{ObjectMeta: metav1.ObjectMeta{Name: "app", Namespace: "team"}, Spec: appsv1.DeploymentSpec{Template: corev1.PodTemplateSpec{Spec: spec}}}},
ClusterVersion: version, LimitRanges: lrs,
}
}
func assertResourceCheck(t *testing.T, result *ScanResults, id string, evaluated, passed int) {
t.Helper()
if got := result.CheckCounts[id]; got.Evaluated != evaluated || got.Passed != passed {
t.Errorf("%s counts = %+v, want evaluated=%d passed=%d", id, got, evaluated, passed)
}
found := false
for _, f := range result.Findings {
found = found || f.CheckID == id
}
if found != (evaluated > passed) {
t.Errorf("%s finding=%v, evaluated=%d passed=%d", id, found, evaluated, passed)
}
}
func TestResourceDefaultsVersionAndGateAgreement(t *testing.T) {
spec := corev1.PodSpec{
Resources: &corev1.ResourceRequirements{Requests: resourceBudgets("500m", "")},
Containers: []corev1.Container{{Name: "app", Resources: corev1.ResourceRequirements{Requests: resourceBudgets("", "128Mi")}}, {Name: "sidecar"}},
}
for _, version := range []string{"v1.32.0", "1.33", "v1.34.1-gke.100", "v1.36.1-eks-a1", "1.37", "v1.37.1", "", "not-a-version", "2.0"} {
t.Run(version, func(t *testing.T) {
result := RunChecks(resourceTemplateInput(spec, version))
unknown := version == "1.37" || version == "v1.37.1" || version == "" || version == "not-a-version" || version == "2.0"
if unknown {
assertResourceCheck(t, result, "memoryRequestMissing", 0, 0)
} else {
assertResourceCheck(t, result, "memoryRequestMissing", 1, 0)
}
if slices.Contains(result.MissingInputs, "pod-resource-defaulting") != unknown {
t.Errorf("unexpected missing inputs: %v", result.MissingInputs)
}
assertResourceCheck(t, result, "cpuRequestMissing", 1, 1)
assertResourceCheck(t, result, "cpuLimitMissing", 1, 0)
})
}
// Equality with the aggregate is configured coverage, not a sizing failure.
spec.Resources.Requests[corev1.ResourceMemory] = resource.MustParse("128Mi")
assertResourceCheck(t, RunChecks(resourceTemplateInput(spec, "1.37")), "memoryRequestMissing", 1, 1)
}
func TestStoredPodResourcesAreNotDefaultedAgain(t *testing.T) {
defaults := &corev1.LimitRange{ObjectMeta: metav1.ObjectMeta{Namespace: "team"}, Spec: corev1.LimitRangeSpec{Limits: []corev1.LimitRangeItem{{Type: corev1.LimitTypeContainer, DefaultRequest: resourceBudgets("100m", "64Mi"), Default: resourceBudgets("500m", "128Mi")}}}}
for _, lrs := range [][]*corev1.LimitRange{nil, {}, {defaults}} {
pod := &corev1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "stored", Namespace: "team"}, Spec: corev1.PodSpec{Containers: []corev1.Container{{Name: "app", Resources: corev1.ResourceRequirements{Limits: resourceBudgets("500m", "128Mi")}}}}}
before := pod.DeepCopy()
input := &CheckInput{Pods: []*corev1.Pod{pod}, LimitRanges: lrs, ClusterVersion: "1.37"}
result := RunChecks(input)
assertResourceCheck(t, result, "cpuRequestMissing", 1, 0)
assertResourceCheck(t, result, "memoryRequestMissing", 1, 0)
assertResourceCheck(t, result, "cpuLimitMissing", 1, 1)
pod.Spec.Resources = &corev1.ResourceRequirements{Requests: resourceBudgets("500m", "128Mi")}
assertResourceCheck(t, RunChecks(input), "cpuRequestMissing", 1, 1)
pod.Spec.Resources = nil
if !reflect.DeepEqual(pod, before) {
t.Fatal("audit mutated stored Pod")
}
}
}
func TestResourceDefaultsKeepExplicitZero(t *testing.T) {
defaults := &corev1.LimitRange{ObjectMeta: metav1.ObjectMeta{Namespace: "team"}, Spec: corev1.LimitRangeSpec{Limits: []corev1.LimitRangeItem{{Type: corev1.LimitTypeContainer, DefaultRequest: resourceBudgets("100m", "64Mi"), Default: resourceBudgets("500m", "128Mi")}}}}
for _, version := range []string{"1.35", "1.37", ""} {
spec := corev1.PodSpec{Containers: []corev1.Container{{Name: "zero", Resources: corev1.ResourceRequirements{Requests: resourceBudgets("0", "0"), Limits: resourceBudgets("0", "0")}}}}
result := RunChecks(resourceTemplateInput(spec, version, defaults))
for _, id := range podSpecEfficiencyCheckIDs {
assertResourceCheck(t, result, id, 1, 0)
}
}
}
func TestResourceDefaultsForContainerLimits(t *testing.T) {
spec := corev1.PodSpec{Containers: []corev1.Container{{Name: "app", Resources: corev1.ResourceRequirements{Limits: resourceBudgets("100m", "64Mi")}}}}
for _, id := range podSpecEfficiencyCheckIDs {
assertResourceCheck(t, RunChecks(resourceTemplateInput(spec, "1.37")), id, 1, 1)
}
}
func TestResourceDefaultsSidecarsAndOrdinaryInit(t *testing.T) {
spec := corev1.PodSpec{
Containers: []corev1.Container{{Name: "app", Resources: corev1.ResourceRequirements{Requests: resourceBudgets("100m", "64Mi"), Limits: resourceBudgets("500m", "128Mi")}}},
InitContainers: []corev1.Container{{Name: "setup"}},
}
for _, id := range podSpecEfficiencyCheckIDs {
assertResourceCheck(t, RunChecks(resourceTemplateInput(spec, "1.37")), id, 1, 1)
}
spec.InitContainers = append(spec.InitContainers, corev1.Container{Name: "helper", RestartPolicy: ptr(corev1.ContainerRestartPolicyAlways)})
result := RunChecks(resourceTemplateInput(spec, "1.37"))
for _, id := range podSpecEfficiencyCheckIDs {
assertResourceCheck(t, result, id, 1, 0)
}
for _, f := range result.Findings {
if slices.Contains(podSpecEfficiencyCheckIDs, f.CheckID) && f.Message != "Sidecar \"helper\" has no "+map[string]string{"cpuRequestMissing": "CPU request", "memoryRequestMissing": "memory request", "cpuLimitMissing": "CPU limit", "memoryLimitMissing": "memory limit"}[f.CheckID] {
t.Errorf("wrong sidecar attribution: %s", f.Message)
}
}
spec.Resources = &corev1.ResourceRequirements{Requests: resourceBudgets("500m", "256Mi"), Limits: resourceBudgets("1", "512Mi")}
for _, id := range podSpecEfficiencyCheckIDs {
assertResourceCheck(t, RunChecks(resourceTemplateInput(spec, "1.37")), id, 1, 1)
}
}
func TestResourceDefaultsZeroLimitsCanDeriveSharedLimits(t *testing.T) {
spec := corev1.PodSpec{Resources: &corev1.ResourceRequirements{Requests: resourceBudgets("100m", "64Mi")}, Containers: []corev1.Container{{Name: "app", Resources: corev1.ResourceRequirements{Limits: resourceBudgets("0", "0")}}}}
for _, id := range []string{"cpuLimitMissing", "memoryLimitMissing"} {
assertResourceCheck(t, RunChecks(resourceTemplateInput(spec, "1.35")), id, 1, 0)
assertResourceCheck(t, RunChecks(resourceTemplateInput(spec, "1.37")), id, 0, 0)
}
}
func TestResourceDefaultsConflictingLimitRanges(t *testing.T) {
makeRange := func(cpu string) *corev1.LimitRange {
return &corev1.LimitRange{ObjectMeta: metav1.ObjectMeta{Namespace: "team"}, Spec: corev1.LimitRangeSpec{Limits: []corev1.LimitRangeItem{{Type: corev1.LimitTypeContainer, DefaultRequest: resourceBudgets(cpu, "")}}}}
}
zero, positive := makeRange("0"), makeRange("100m")
for _, lrs := range [][]*corev1.LimitRange{{zero, positive}, {positive, zero}} {
spec := corev1.PodSpec{Containers: []corev1.Container{{Name: "app"}}}
result := RunChecks(resourceTemplateInput(spec, "1.35", lrs...))
assertResourceCheck(t, result, "cpuRequestMissing", 0, 0)
assertResourceCheck(t, result, "memoryRequestMissing", 1, 0)
spec.Containers = append(spec.Containers, corev1.Container{Name: "explicit-zero", Resources: corev1.ResourceRequirements{Requests: resourceBudgets("0", "")}})
assertResourceCheck(t, RunChecks(resourceTemplateInput(spec, "1.35", lrs...)), "cpuRequestMissing", 1, 0)
}
}
func TestResourceDefaultsHugepageActivation(t *testing.T) {
spec := corev1.PodSpec{Resources: &corev1.ResourceRequirements{Requests: resourceBudgets("500m", "")}, Containers: []corev1.Container{{Name: "app", Resources: corev1.ResourceRequirements{Requests: resourceBudgets("", "128Mi"), Limits: corev1.ResourceList{corev1.ResourceName("hugepages-2Mi"): resource.MustParse("2Mi")}}}, {Name: "sidecar"}}}
assertResourceCheck(t, RunChecks(resourceTemplateInput(spec, "1.32")), "memoryRequestMissing", 1, 0)
assertResourceCheck(t, RunChecks(resourceTemplateInput(spec, "1.33")), "memoryRequestMissing", 1, 1)
assertResourceCheck(t, RunChecks(resourceTemplateInput(spec, "1.35")), "memoryRequestMissing", 1, 1)
spec.Resources.Requests[corev1.ResourceName("hugepages-2Mi")] = resource.MustParse("2Mi")
for _, version := range []string{"1.33", "1.34"} {
candidate := podResourceCandidates(spec, false, version, nil)[0]
_, present := candidate.Resources.Limits[corev1.ResourceName("hugepages-2Mi")]
if present != (version == "1.33") {
t.Errorf("%s: unexpected hugepage limit defaulting with explicit request", version)
}
}
}
func TestResourceDefaultsAfterMetadataWebhookPatch(t *testing.T) {
name := corev1.ResourceName("hugepages-2Mi")
lr := &corev1.LimitRange{ObjectMeta: metav1.ObjectMeta{Namespace: "team"}, Spec: corev1.LimitRangeSpec{Limits: []corev1.LimitRangeItem{{Type: corev1.LimitTypeContainer, Default: corev1.ResourceList{name: resource.MustParse("2Mi")}, DefaultRequest: corev1.ResourceList{name: resource.MustParse("2Mi")}}}}}
spec := corev1.PodSpec{Resources: &corev1.ResourceRequirements{Requests: resourceBudgets("500m", "")}, Containers: []corev1.Container{{Name: "app", Resources: corev1.ResourceRequirements{Requests: resourceBudgets("", "128Mi")}}, {Name: "helper", Resources: corev1.ResourceRequirements{Requests: resourceBudgets("100m", "")}}}}
for _, version := range []string{"1.33", "1.35", "1.37", ""} {
result := RunChecks(resourceTemplateInput(spec, version, lr))
assertResourceCheck(t, result, "memoryRequestMissing", 0, 0)
if !slices.Contains(result.MissingInputs, "pod-resource-defaulting") {
t.Errorf("%s: missing uncertain admission result", version)
}
}
assertResourceCheck(t, RunChecks(resourceTemplateInput(spec, "1.32", lr)), "memoryRequestMissing", 1, 0)
}
func TestResourceDefaultCandidatesDoNotMutateInputs(t *testing.T) {
spec := corev1.PodSpec{Resources: &corev1.ResourceRequirements{Limits: resourceBudgets("1", "512Mi")}, Containers: []corev1.Container{{Name: "app", Resources: corev1.ResourceRequirements{Limits: resourceBudgets("100m", "64Mi")}}}, InitContainers: []corev1.Container{{Name: "setup", Resources: corev1.ResourceRequirements{Limits: resourceBudgets("200m", "128Mi")}}}}
lr := &corev1.LimitRange{Spec: corev1.LimitRangeSpec{Limits: []corev1.LimitRangeItem{{Type: corev1.LimitTypeContainer, Default: resourceBudgets("500m", "256Mi")}}}}
before, lrBefore := spec.DeepCopy(), lr.DeepCopy()
for _, candidate := range podResourceCandidates(spec, false, "1.37", []*corev1.LimitRange{lr}) {
candidate.Resources.Requests[corev1.ResourceCPU] = resource.MustParse("99")
}
if !reflect.DeepEqual(spec, *before) || !reflect.DeepEqual(lr, lrBefore) {
t.Fatal("defaulting mutated cached resources")
}
}