From b9e0d4ad6667d0882ba57b34ca97ae77ef2585c0 Mon Sep 17 00:00:00 2001 From: Kevin Torres Date: Wed, 26 Feb 2025 19:38:32 +0000 Subject: [PATCH] Unit tests for pod level hugepage resources --- pkg/apis/core/v1/defaults_test.go | 292 ++++++++++++++++++ pkg/apis/core/validation/validation_test.go | 173 +++++++---- .../kuberuntime_container_linux_test.go | 2 +- .../plugins/noderesources/fit_test.go | 60 +++- pkg/volume/emptydir/empty_dir_test.go | 126 +++++++- .../resource/helpers_test.go | 153 +++++++++ 6 files changed, 745 insertions(+), 61 deletions(-) diff --git a/pkg/apis/core/v1/defaults_test.go b/pkg/apis/core/v1/defaults_test.go index c5e3a9bb518..f09c450eb4f 100644 --- a/pkg/apis/core/v1/defaults_test.go +++ b/pkg/apis/core/v1/defaults_test.go @@ -1222,6 +1222,298 @@ func TestPodResourcesDefaults(t *testing.T) { }, }, }, + }, { + name: "pod hugepages requests=unset limits=unset, container hugepages requests=unset limits=set", + podLevelResourcesEnabled: true, + podResources: &v1.ResourceRequirements{ + Limits: v1.ResourceList{ + "cpu": resource.MustParse("5m"), + }, + }, + containers: []v1.Container{ + { + Resources: v1.ResourceRequirements{ + Limits: v1.ResourceList{ + "cpu": resource.MustParse("2m"), + v1.ResourceHugePagesPrefix + "2Mi": resource.MustParse("4Mi"), + }, + }, + }, { + Resources: v1.ResourceRequirements{ + Limits: v1.ResourceList{ + "cpu": resource.MustParse("1m"), + v1.ResourceHugePagesPrefix + "2Mi": resource.MustParse("2Mi"), + }, + }, + }, + }, + expectedPodSpec: v1.PodSpec{ + Resources: &v1.ResourceRequirements{ + Requests: v1.ResourceList{ + "cpu": resource.MustParse("3m"), + v1.ResourceHugePagesPrefix + "2Mi": resource.MustParse("6Mi"), + }, + Limits: v1.ResourceList{ + "cpu": resource.MustParse("5m"), + v1.ResourceHugePagesPrefix + "2Mi": resource.MustParse("6Mi"), + }, + }, + Containers: []v1.Container{ + { + Resources: v1.ResourceRequirements{ + Requests: v1.ResourceList{ + "cpu": resource.MustParse("2m"), + v1.ResourceHugePagesPrefix + "2Mi": resource.MustParse("4Mi"), + }, + Limits: v1.ResourceList{ + "cpu": resource.MustParse("2m"), + v1.ResourceHugePagesPrefix + "2Mi": resource.MustParse("4Mi"), + }, + }, + }, { + Resources: v1.ResourceRequirements{ + Requests: v1.ResourceList{ + "cpu": resource.MustParse("1m"), + v1.ResourceHugePagesPrefix + "2Mi": resource.MustParse("2Mi"), + }, + Limits: v1.ResourceList{ + "cpu": resource.MustParse("1m"), + v1.ResourceHugePagesPrefix + "2Mi": resource.MustParse("2Mi"), + }, + }, + }, + }, + }, + }, { + name: "pod hugepages requests=unset limits=set, container hugepages requests=unset limits=set", + podLevelResourcesEnabled: true, + podResources: &v1.ResourceRequirements{ + Limits: v1.ResourceList{ + "cpu": resource.MustParse("5m"), + v1.ResourceHugePagesPrefix + "2Mi": resource.MustParse("10Mi"), + }, + }, + containers: []v1.Container{ + { + Resources: v1.ResourceRequirements{ + Limits: v1.ResourceList{ + "cpu": resource.MustParse("2m"), + v1.ResourceHugePagesPrefix + "2Mi": resource.MustParse("4Mi"), + }, + }, + }, { + Resources: v1.ResourceRequirements{ + Limits: v1.ResourceList{ + "cpu": resource.MustParse("1m"), + v1.ResourceHugePagesPrefix + "2Mi": resource.MustParse("2Mi"), + }, + }, + }, + }, + expectedPodSpec: v1.PodSpec{ + Resources: &v1.ResourceRequirements{ + Requests: v1.ResourceList{ + "cpu": resource.MustParse("3m"), + v1.ResourceHugePagesPrefix + "2Mi": resource.MustParse("10Mi"), + }, + Limits: v1.ResourceList{ + "cpu": resource.MustParse("5m"), + v1.ResourceHugePagesPrefix + "2Mi": resource.MustParse("10Mi"), + }, + }, + Containers: []v1.Container{ + { + Resources: v1.ResourceRequirements{ + Requests: v1.ResourceList{ + "cpu": resource.MustParse("2m"), + v1.ResourceHugePagesPrefix + "2Mi": resource.MustParse("4Mi"), + }, + Limits: v1.ResourceList{ + "cpu": resource.MustParse("2m"), + v1.ResourceHugePagesPrefix + "2Mi": resource.MustParse("4Mi"), + }, + }, + }, { + Resources: v1.ResourceRequirements{ + Requests: v1.ResourceList{ + "cpu": resource.MustParse("1m"), + v1.ResourceHugePagesPrefix + "2Mi": resource.MustParse("2Mi"), + }, + Limits: v1.ResourceList{ + "cpu": resource.MustParse("1m"), + v1.ResourceHugePagesPrefix + "2Mi": resource.MustParse("2Mi"), + }, + }, + }, + }, + }, + }, { + name: "pod hugepages requests=set limits=set, container hugepages requests=unset limits=set", + podLevelResourcesEnabled: true, + podResources: &v1.ResourceRequirements{ + Limits: v1.ResourceList{ + "cpu": resource.MustParse("5m"), + v1.ResourceHugePagesPrefix + "2Mi": resource.MustParse("10Mi"), + }, + Requests: v1.ResourceList{ + "cpu": resource.MustParse("5m"), + v1.ResourceHugePagesPrefix + "2Mi": resource.MustParse("10Mi"), + }, + }, + containers: []v1.Container{ + { + Resources: v1.ResourceRequirements{ + Limits: v1.ResourceList{ + "cpu": resource.MustParse("2m"), + v1.ResourceHugePagesPrefix + "2Mi": resource.MustParse("4Mi"), + }, + }, + }, { + Resources: v1.ResourceRequirements{ + Limits: v1.ResourceList{ + "cpu": resource.MustParse("1m"), + v1.ResourceHugePagesPrefix + "2Mi": resource.MustParse("2Mi"), + }, + }, + }, + }, + expectedPodSpec: v1.PodSpec{ + Resources: &v1.ResourceRequirements{ + Requests: v1.ResourceList{ + "cpu": resource.MustParse("5m"), + v1.ResourceHugePagesPrefix + "2Mi": resource.MustParse("10Mi"), + }, + Limits: v1.ResourceList{ + "cpu": resource.MustParse("5m"), + v1.ResourceHugePagesPrefix + "2Mi": resource.MustParse("10Mi"), + }, + }, + Containers: []v1.Container{ + { + Resources: v1.ResourceRequirements{ + Requests: v1.ResourceList{ + "cpu": resource.MustParse("2m"), + v1.ResourceHugePagesPrefix + "2Mi": resource.MustParse("4Mi"), + }, + Limits: v1.ResourceList{ + "cpu": resource.MustParse("2m"), + v1.ResourceHugePagesPrefix + "2Mi": resource.MustParse("4Mi"), + }, + }, + }, { + Resources: v1.ResourceRequirements{ + Requests: v1.ResourceList{ + "cpu": resource.MustParse("1m"), + v1.ResourceHugePagesPrefix + "2Mi": resource.MustParse("2Mi"), + }, + Limits: v1.ResourceList{ + "cpu": resource.MustParse("1m"), + v1.ResourceHugePagesPrefix + "2Mi": resource.MustParse("2Mi"), + }, + }, + }, + }, + }, + }, { + name: "pod hugepages requests=set limits=set, container hugepages requests=unset limits=unset", + podLevelResourcesEnabled: true, + podResources: &v1.ResourceRequirements{ + Limits: v1.ResourceList{ + "cpu": resource.MustParse("5m"), + v1.ResourceHugePagesPrefix + "2Mi": resource.MustParse("10Mi"), + }, + Requests: v1.ResourceList{ + "cpu": resource.MustParse("5m"), + v1.ResourceHugePagesPrefix + "2Mi": resource.MustParse("10Mi"), + }, + }, + containers: []v1.Container{ + { + Resources: v1.ResourceRequirements{}, + }, + }, + expectedPodSpec: v1.PodSpec{ + Resources: &v1.ResourceRequirements{ + Requests: v1.ResourceList{ + "cpu": resource.MustParse("5m"), + v1.ResourceHugePagesPrefix + "2Mi": resource.MustParse("10Mi"), + }, + Limits: v1.ResourceList{ + "cpu": resource.MustParse("5m"), + v1.ResourceHugePagesPrefix + "2Mi": resource.MustParse("10Mi"), + }, + }, + Containers: []v1.Container{ + { + Resources: v1.ResourceRequirements{}, + }, + }, + }, + }, { + name: "pod hugepages requests=unset limits=set, container hugepages requests=unset limits=set different hugepagesizes between pod and container level", + podLevelResourcesEnabled: true, + podResources: &v1.ResourceRequirements{ + Limits: v1.ResourceList{ + "cpu": resource.MustParse("5m"), + v1.ResourceHugePagesPrefix + "2Mi": resource.MustParse("10Mi"), + }, + }, + containers: []v1.Container{ + { + Resources: v1.ResourceRequirements{ + Limits: v1.ResourceList{ + "cpu": resource.MustParse("2m"), + v1.ResourceHugePagesPrefix + "1Gi": resource.MustParse("1Gi"), + }, + }, + }, { + Resources: v1.ResourceRequirements{ + Limits: v1.ResourceList{ + "cpu": resource.MustParse("1m"), + v1.ResourceHugePagesPrefix + "2Mi": resource.MustParse("2Mi"), + }, + }, + }, + }, + expectedPodSpec: v1.PodSpec{ + Resources: &v1.ResourceRequirements{ + Requests: v1.ResourceList{ + "cpu": resource.MustParse("3m"), + v1.ResourceHugePagesPrefix + "2Mi": resource.MustParse("10Mi"), + v1.ResourceHugePagesPrefix + "1Gi": resource.MustParse("1Gi"), + }, + Limits: v1.ResourceList{ + "cpu": resource.MustParse("5m"), + v1.ResourceHugePagesPrefix + "2Mi": resource.MustParse("10Mi"), + v1.ResourceHugePagesPrefix + "1Gi": resource.MustParse("1Gi"), + }, + }, + Containers: []v1.Container{ + { + Resources: v1.ResourceRequirements{ + Requests: v1.ResourceList{ + "cpu": resource.MustParse("2m"), + v1.ResourceHugePagesPrefix + "1Gi": resource.MustParse("1Gi"), + }, + Limits: v1.ResourceList{ + "cpu": resource.MustParse("2m"), + v1.ResourceHugePagesPrefix + "1Gi": resource.MustParse("1Gi"), + }, + }, + }, { + Resources: v1.ResourceRequirements{ + Requests: v1.ResourceList{ + "cpu": resource.MustParse("1m"), + v1.ResourceHugePagesPrefix + "2Mi": resource.MustParse("2Mi"), + }, + Limits: v1.ResourceList{ + "cpu": resource.MustParse("1m"), + v1.ResourceHugePagesPrefix + "2Mi": resource.MustParse("2Mi"), + }, + }, + }, + }, + }, }} for _, tc := range cases { diff --git a/pkg/apis/core/validation/validation_test.go b/pkg/apis/core/validation/validation_test.go index 23b4f417f5e..f009b0fd86f 100644 --- a/pkg/apis/core/validation/validation_test.go +++ b/pkg/apis/core/validation/validation_test.go @@ -19024,7 +19024,7 @@ func TestValidatePodResourceConsistency(t *testing.T) { }, }, }, { - name: "indivdual container limits greater than pod limits", + name: "individual container limits greater than pod limits", podResources: core.ResourceRequirements{ Limits: core.ResourceList{ core.ResourceCPU: resource.MustParse("10"), @@ -19084,6 +19084,8 @@ func TestValidatePodResourceNames(t *testing.T) { {"memory", false}, {"cpu", false}, {"storage", true}, + {v1.ResourceHugePagesPrefix + "2Mi", false}, + {v1.ResourceHugePagesPrefix + "1Gi", false}, {"requests.cpu", true}, {"requests.memory", true}, {"requests.storage", true}, @@ -19128,6 +19130,8 @@ func TestValidateResourceNames(t *testing.T) { {"memory", true, ""}, {"cpu", true, ""}, {"storage", true, ""}, + {v1.ResourceHugePagesPrefix + "2Mi", true, ""}, + {v1.ResourceHugePagesPrefix + "1Gi", true, ""}, {"requests.cpu", true, ""}, {"requests.memory", true, ""}, {"requests.storage", true, ""}, @@ -23624,6 +23628,48 @@ func TestValidateResourceRequirements(t *testing.T) { }, }, validateFn: ValidateContainerResourceRequirements, + }, { + name: "container resource hugepage with cpu or memory", + requirements: core.ResourceRequirements{ + Limits: core.ResourceList{ + core.ResourceName(core.ResourceHugePagesPrefix + "2Mi"): resource.MustParse("2Mi"), + core.ResourceCPU: resource.MustParse("10"), + }, + Requests: core.ResourceList{ + core.ResourceName(core.ResourceHugePagesPrefix + "2Mi"): resource.MustParse("2Mi"), + }, + }, + validateFn: ValidateContainerResourceRequirements, + }, { + name: "container resource hugepage limit without request", + requirements: core.ResourceRequirements{ + Limits: core.ResourceList{ + core.ResourceMemory: resource.MustParse("2Mi"), + core.ResourceName(core.ResourceHugePagesPrefix + "2Mi"): resource.MustParse("2Mi"), + }, + }, + validateFn: ValidateContainerResourceRequirements, + }, { + name: "pod resource hugepages with cpu or memory", + requirements: core.ResourceRequirements{ + Limits: core.ResourceList{ + core.ResourceName(core.ResourceHugePagesPrefix + "2Mi"): resource.MustParse("2Mi"), + }, + Requests: core.ResourceList{ + core.ResourceMemory: resource.MustParse("2Mi"), + core.ResourceName(core.ResourceHugePagesPrefix + "2Mi"): resource.MustParse("2Mi"), + }, + }, + validateFn: validatePodResourceRequirements, + }, { + name: "pod resource hugepages limit without request", + requirements: core.ResourceRequirements{ + Limits: core.ResourceList{ + core.ResourceMemory: resource.MustParse("2Mi"), + core.ResourceName(core.ResourceHugePagesPrefix + "2Mi"): resource.MustParse("2Mi"), + }, + }, + validateFn: validatePodResourceRequirements, }, { name: "limits and requests of memory resource are equal", requirements: core.ResourceRequirements{ @@ -23686,62 +23732,81 @@ func TestValidateResourceRequirements(t *testing.T) { validateFn func(requirements *core.ResourceRequirements, podClaimNames sets.Set[string], fldPath *field.Path, opts PodValidationOptions) field.ErrorList - }{{ - name: "hugepage resource without cpu or memory", - requirements: core.ResourceRequirements{ - Limits: core.ResourceList{ - core.ResourceName(core.ResourceHugePagesPrefix + "2Mi"): resource.MustParse("2Mi"), + }{ + { + name: "container resource hugepage without cpu or memory", + requirements: core.ResourceRequirements{ + Limits: core.ResourceList{ + core.ResourceName(core.ResourceHugePagesPrefix + "2Mi"): resource.MustParse("2Mi"), + }, + Requests: core.ResourceList{ + core.ResourceName(core.ResourceHugePagesPrefix + "2Mi"): resource.MustParse("2Mi"), + }, }, - Requests: core.ResourceList{ - core.ResourceName(core.ResourceHugePagesPrefix + "2Mi"): resource.MustParse("2Mi"), + validateFn: ValidateContainerResourceRequirements, + }, { + name: "container resource hugepage without limit", + requirements: core.ResourceRequirements{ + Requests: core.ResourceList{ + core.ResourceMemory: resource.MustParse("2Mi"), + core.ResourceName(core.ResourceHugePagesPrefix + "2Mi"): resource.MustParse("2Mi"), + }, }, + validateFn: ValidateContainerResourceRequirements, + }, { + name: "pod resource hugepages without cpu or memory", + requirements: core.ResourceRequirements{ + Limits: core.ResourceList{ + core.ResourceName(core.ResourceHugePagesPrefix + "2Mi"): resource.MustParse("2Mi"), + }, + Requests: core.ResourceList{ + core.ResourceName(core.ResourceHugePagesPrefix + "2Mi"): resource.MustParse("2Mi"), + }, + }, + validateFn: validatePodResourceRequirements, + }, { + name: "pod resource hugepages request without limit", + requirements: core.ResourceRequirements{ + Requests: core.ResourceList{ + core.ResourceMemory: resource.MustParse("2Mi"), + core.ResourceName(core.ResourceHugePagesPrefix + "2Mi"): resource.MustParse("2Mi"), + }, + }, + validateFn: validatePodResourceRequirements, + }, { + name: "pod resource with ephemeral-storage", + requirements: core.ResourceRequirements{ + Limits: core.ResourceList{ + core.ResourceName(core.ResourceEphemeralStorage): resource.MustParse("2Mi"), + }, + Requests: core.ResourceList{ + core.ResourceName(core.ResourceEphemeralStorage + "2Mi"): resource.MustParse("2Mi"), + }, + }, + validateFn: validatePodResourceRequirements, + }, { + name: "pod resource with unsupported prefixed resources", + requirements: core.ResourceRequirements{ + Limits: core.ResourceList{ + core.ResourceName("kubernetesio/" + core.ResourceCPU): resource.MustParse("2"), + }, + Requests: core.ResourceList{ + core.ResourceName("kubernetesio/" + core.ResourceMemory): resource.MustParse("2"), + }, + }, + validateFn: validatePodResourceRequirements, + }, { + name: "pod resource with unsupported native resources", + requirements: core.ResourceRequirements{ + Limits: core.ResourceList{ + core.ResourceName("kubernetes.io/" + strings.Repeat("a", 63)): resource.MustParse("2"), + }, + Requests: core.ResourceList{ + core.ResourceName("kubernetes.io/" + strings.Repeat("a", 63)): resource.MustParse("2"), + }, + }, + validateFn: validatePodResourceRequirements, }, - validateFn: ValidateContainerResourceRequirements, - }, { - name: "pod resource with hugepages", - requirements: core.ResourceRequirements{ - Limits: core.ResourceList{ - core.ResourceName(core.ResourceHugePagesPrefix + "2Mi"): resource.MustParse("2Mi"), - }, - Requests: core.ResourceList{ - core.ResourceName(core.ResourceHugePagesPrefix + "2Mi"): resource.MustParse("2Mi"), - }, - }, - validateFn: validatePodResourceRequirements, - }, { - name: "pod resource with ephemeral-storage", - requirements: core.ResourceRequirements{ - Limits: core.ResourceList{ - core.ResourceName(core.ResourceEphemeralStorage): resource.MustParse("2Mi"), - }, - Requests: core.ResourceList{ - core.ResourceName(core.ResourceEphemeralStorage + "2Mi"): resource.MustParse("2Mi"), - }, - }, - validateFn: validatePodResourceRequirements, - }, { - name: "pod resource with unsupported prefixed resources", - requirements: core.ResourceRequirements{ - Limits: core.ResourceList{ - core.ResourceName("kubernetesio/" + core.ResourceCPU): resource.MustParse("2"), - }, - Requests: core.ResourceList{ - core.ResourceName("kubernetesio/" + core.ResourceMemory): resource.MustParse("2"), - }, - }, - validateFn: validatePodResourceRequirements, - }, { - name: "pod resource with unsupported native resources", - requirements: core.ResourceRequirements{ - Limits: core.ResourceList{ - core.ResourceName("kubernetes.io/" + strings.Repeat("a", 63)): resource.MustParse("2"), - }, - Requests: core.ResourceList{ - core.ResourceName("kubernetes.io/" + strings.Repeat("a", 63)): resource.MustParse("2"), - }, - }, - validateFn: validatePodResourceRequirements, - }, { name: "pod resource with unsupported empty native resource name", requirements: core.ResourceRequirements{ diff --git a/pkg/kubelet/kuberuntime/kuberuntime_container_linux_test.go b/pkg/kubelet/kuberuntime/kuberuntime_container_linux_test.go index 6bb6f606966..ae1ad6125d6 100644 --- a/pkg/kubelet/kuberuntime/kuberuntime_container_linux_test.go +++ b/pkg/kubelet/kuberuntime/kuberuntime_container_linux_test.go @@ -665,7 +665,7 @@ func TestGetHugepageLimitsFromResources(t *testing.T) { } } - results := GetHugepageLimitsFromResources(test.resources) + results := GetHugepageLimitsFromResources(&v1.Pod{}, test.resources) if !reflect.DeepEqual(expectedHugepages, results) { t.Errorf("%s test failed. Expected %v but got %v", test.name, expectedHugepages, results) } diff --git a/pkg/scheduler/framework/plugins/noderesources/fit_test.go b/pkg/scheduler/framework/plugins/noderesources/fit_test.go index 84727423ca4..ab46344d906 100644 --- a/pkg/scheduler/framework/plugins/noderesources/fit_test.go +++ b/pkg/scheduler/framework/plugins/noderesources/fit_test.go @@ -543,6 +543,45 @@ func TestEnoughRequests(t *testing.T) { ResourceName: v1.ResourceMemory, Reason: getErrReason(v1.ResourceMemory), Requested: 2, Used: 19, Capacity: 20}, }, }, + { + podLevelResourcesEnabled: true, + pod: newPodLevelResourcesPod( + newResourcePod(), + v1.ResourceRequirements{ + Requests: v1.ResourceList{v1.ResourceCPU: resource.MustParse("3m"), v1.ResourceMemory: resource.MustParse("2"), hugePageResourceA: *resource.NewQuantity(5, resource.BinarySI)}, + }, + ), + nodeInfo: framework.NewNodeInfo(), + name: "pod-level hugepages resource fit", + wantInsufficientResources: []InsufficientResource{}, + }, + { + podLevelResourcesEnabled: true, + pod: newPodLevelResourcesPod( + newResourcePod(framework.Resource{MilliCPU: 1, Memory: 1, ScalarResources: map[v1.ResourceName]int64{hugePageResourceA: 3}}), + v1.ResourceRequirements{ + Requests: v1.ResourceList{v1.ResourceCPU: resource.MustParse("3m"), v1.ResourceMemory: resource.MustParse("2"), hugePageResourceA: *resource.NewQuantity(5, resource.BinarySI)}, + }, + ), + nodeInfo: framework.NewNodeInfo(), + name: "both pod-level and container-level hugepages resource fit", + wantInsufficientResources: []InsufficientResource{}, + }, + { + podLevelResourcesEnabled: true, + pod: newPodLevelResourcesPod( + newResourcePod(), + v1.ResourceRequirements{ + Requests: v1.ResourceList{v1.ResourceCPU: resource.MustParse("3m"), v1.ResourceMemory: resource.MustParse("2"), hugePageResourceA: *resource.NewQuantity(10, resource.BinarySI)}, + }, + ), + nodeInfo: framework.NewNodeInfo(), + name: "pod-level hugepages resource not fit", + wantStatus: framework.NewStatus(framework.Unschedulable, getErrReason(hugePageResourceA)), + wantInsufficientResources: []InsufficientResource{ + {ResourceName: hugePageResourceA, Reason: getErrReason(hugePageResourceA), Requested: 10, Used: 0, Capacity: 5}, + }, + }, { podLevelResourcesEnabled: true, pod: newResourceInitPod(newPodLevelResourcesPod( @@ -1547,8 +1586,25 @@ func TestIsFit(t *testing.T) { pod: st.MakePod().Resources( v1.ResourceRequirements{Requests: v1.ResourceList{v1.ResourceCPU: resource.MustParse("2")}}, ).Obj(), - node: st.MakeNode().Capacity(map[v1.ResourceName]string{v1.ResourceCPU: "2"}).Obj(), - expected: true, + node: st.MakeNode().Capacity(map[v1.ResourceName]string{v1.ResourceCPU: "2"}).Obj(), + podLevelResourcesEnabled: true, + expected: true, + }, + "sufficient pod-level resource hugepages": { + pod: st.MakePod().Resources( + v1.ResourceRequirements{Requests: v1.ResourceList{hugePageResourceA: resource.MustParse("2Mi")}}, + ).Obj(), + node: st.MakeNode().Capacity(map[v1.ResourceName]string{hugePageResourceA: "2Mi"}).Obj(), + podLevelResourcesEnabled: true, + expected: true, + }, + "insufficient pod-level resource hugepages": { + pod: st.MakePod().Resources( + v1.ResourceRequirements{Requests: v1.ResourceList{hugePageResourceA: resource.MustParse("4Mi")}}, + ).Obj(), + node: st.MakeNode().Capacity(map[v1.ResourceName]string{hugePageResourceA: "2Mi"}).Obj(), + podLevelResourcesEnabled: true, + expected: false, }, } diff --git a/pkg/volume/emptydir/empty_dir_test.go b/pkg/volume/emptydir/empty_dir_test.go index 5dcf9c0f7b5..73826145871 100644 --- a/pkg/volume/emptydir/empty_dir_test.go +++ b/pkg/volume/emptydir/empty_dir_test.go @@ -26,6 +26,9 @@ import ( "strings" "testing" + utilfeature "k8s.io/apiserver/pkg/util/feature" + featuregatetesting "k8s.io/component-base/featuregate/testing" + "k8s.io/kubernetes/pkg/features" "k8s.io/kubernetes/pkg/kubelet/util/swap" v1 "k8s.io/api/core/v1" @@ -373,10 +376,11 @@ func TestMetrics(t *testing.T) { func TestGetHugePagesMountOptions(t *testing.T) { testCases := map[string]struct { - pod *v1.Pod - medium v1.StorageMedium - shouldFail bool - expectedResult string + pod *v1.Pod + medium v1.StorageMedium + shouldFail bool + expectedResult string + podLevelResourcesEnabled bool }{ "ProperValues": { pod: &v1.Pod{ @@ -605,10 +609,124 @@ func TestGetHugePagesMountOptions(t *testing.T) { shouldFail: true, expectedResult: "", }, + "PodLevelResourcesSinglePageSize": { + podLevelResourcesEnabled: true, + pod: &v1.Pod{ + Spec: v1.PodSpec{ + Resources: &v1.ResourceRequirements{ + Requests: v1.ResourceList{ + v1.ResourceName("hugepages-2Mi"): resource.MustParse("100Mi"), + }, + }, + }, + }, + medium: v1.StorageMediumHugePages, + shouldFail: false, + expectedResult: "pagesize=2Mi", + }, + "PodLevelResourcesSinglePageSizeMediumPrefix": { + podLevelResourcesEnabled: true, + pod: &v1.Pod{ + Spec: v1.PodSpec{ + Resources: &v1.ResourceRequirements{ + Requests: v1.ResourceList{ + v1.ResourceName("hugepages-2Mi"): resource.MustParse("100Mi"), + }, + }, + }, + }, + medium: v1.StorageMediumHugePagesPrefix + "2Mi", + shouldFail: false, + expectedResult: "pagesize=2Mi", + }, + "PodLevelResourcesMultiPageSize": { + podLevelResourcesEnabled: true, + pod: &v1.Pod{ + Spec: v1.PodSpec{ + Resources: &v1.ResourceRequirements{ + Requests: v1.ResourceList{ + v1.ResourceName("hugepages-1Gi"): resource.MustParse("2Gi"), + v1.ResourceName("hugepages-2Mi"): resource.MustParse("100Mi"), + }, + }, + }, + }, + medium: v1.StorageMediumHugePages, + shouldFail: true, + expectedResult: "", + }, + "PodLevelResourcesMultiPageSizeMediumPrefix": { + podLevelResourcesEnabled: true, + pod: &v1.Pod{ + Spec: v1.PodSpec{ + Resources: &v1.ResourceRequirements{ + Requests: v1.ResourceList{ + v1.ResourceName("hugepages-1Gi"): resource.MustParse("2Gi"), + v1.ResourceName("hugepages-2Mi"): resource.MustParse("100Mi"), + }, + }, + }, + }, + medium: v1.StorageMediumHugePagesPrefix + "2Mi", + shouldFail: false, + expectedResult: "pagesize=2Mi", + }, + "PodAndContainerLevelResourcesMultiPageSizeHugePagesMedium": { + podLevelResourcesEnabled: true, + pod: &v1.Pod{ + Spec: v1.PodSpec{ + Resources: &v1.ResourceRequirements{ + Requests: v1.ResourceList{ + v1.ResourceName("hugepages-1Gi"): resource.MustParse("2Gi"), + }, + }, + Containers: []v1.Container{ + { + Resources: v1.ResourceRequirements{ + Requests: v1.ResourceList{ + v1.ResourceName("hugepages-2Mi"): resource.MustParse("100Mi"), + }, + }, + }, + }, + }, + }, + medium: v1.StorageMediumHugePages, + shouldFail: true, + expectedResult: "", + }, + "PodAndContainerLevelResourcesMultiPageSizeHugePagesMediumPrefix": { + podLevelResourcesEnabled: true, + pod: &v1.Pod{ + Spec: v1.PodSpec{ + Resources: &v1.ResourceRequirements{ + Requests: v1.ResourceList{ + v1.ResourceName("hugepages-1Gi"): resource.MustParse("2Gi"), + }, + }, + Containers: []v1.Container{ + { + Resources: v1.ResourceRequirements{ + Requests: v1.ResourceList{ + v1.ResourceName("hugepages-2Mi"): resource.MustParse("100Mi"), + }, + }, + }, + }, + }, + }, + medium: v1.StorageMediumHugePagesPrefix + "2Mi", + shouldFail: false, + expectedResult: "pagesize=2Mi", + }, } for testCaseName, testCase := range testCases { t.Run(testCaseName, func(t *testing.T) { + if testCase.podLevelResourcesEnabled { + featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.PodLevelResources, true) + } + value, err := getPageSizeMountOption(testCase.medium, testCase.pod) if testCase.shouldFail && err == nil { t.Errorf("%s: Unexpected success", testCaseName) diff --git a/staging/src/k8s.io/component-helpers/resource/helpers_test.go b/staging/src/k8s.io/component-helpers/resource/helpers_test.go index a5d4657d21f..24e0ca03676 100644 --- a/staging/src/k8s.io/component-helpers/resource/helpers_test.go +++ b/staging/src/k8s.io/component-helpers/resource/helpers_test.go @@ -1710,6 +1710,118 @@ func TestPodLevelResourceRequests(t *testing.T) { opts: PodResourcesOptions{SkipPodLevelResources: false}, expectedRequests: v1.ResourceList{v1.ResourceMemory: resource.MustParse("15Mi"), v1.ResourceCPU: resource.MustParse("18m")}, }, + { + name: "pod-level resources, hugepage request/limit single page size", + podResources: v1.ResourceRequirements{ + Limits: v1.ResourceList{ + v1.ResourceHugePagesPrefix + "2Mi": resource.MustParse("2Mi"), + }, + Requests: v1.ResourceList{ + v1.ResourceMemory: resource.MustParse("10Mi"), + v1.ResourceHugePagesPrefix + "2Mi": resource.MustParse("2Mi"), + }, + }, + opts: PodResourcesOptions{SkipPodLevelResources: false}, + expectedRequests: v1.ResourceList{v1.ResourceMemory: resource.MustParse("10Mi"), v1.ResourceHugePagesPrefix + "2Mi": resource.MustParse("2Mi")}, + }, + { + name: "pod-level resources, hugepage request/limit multiple page sizes", + podResources: v1.ResourceRequirements{ + Limits: v1.ResourceList{ + v1.ResourceHugePagesPrefix + "2Mi": resource.MustParse("2Mi"), + v1.ResourceHugePagesPrefix + "1Gi": resource.MustParse("1Gi"), + }, + Requests: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("1"), + v1.ResourceHugePagesPrefix + "2Mi": resource.MustParse("2Mi"), + v1.ResourceHugePagesPrefix + "1Gi": resource.MustParse("1Gi"), + }, + }, + opts: PodResourcesOptions{SkipPodLevelResources: false}, + expectedRequests: v1.ResourceList{v1.ResourceCPU: resource.MustParse("1"), v1.ResourceHugePagesPrefix + "2Mi": resource.MustParse("2Mi"), v1.ResourceHugePagesPrefix + "1Gi": resource.MustParse("1Gi")}, + }, + { + name: "pod-level resources, container-level resources, hugepage request/limit single page size", + podResources: v1.ResourceRequirements{ + Limits: v1.ResourceList{ + v1.ResourceHugePagesPrefix + "2Mi": resource.MustParse("10Mi"), + }, + Requests: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("1"), + v1.ResourceHugePagesPrefix + "2Mi": resource.MustParse("10Mi"), + }, + }, + containers: []v1.Container{ + { + Resources: v1.ResourceRequirements{ + Limits: v1.ResourceList{ + v1.ResourceHugePagesPrefix + "2Mi": resource.MustParse("6Mi"), + }, + Requests: v1.ResourceList{ + v1.ResourceHugePagesPrefix + "2Mi": resource.MustParse("6Mi"), + }, + }, + }, + }, + opts: PodResourcesOptions{SkipPodLevelResources: false}, + expectedRequests: v1.ResourceList{v1.ResourceCPU: resource.MustParse("1"), v1.ResourceHugePagesPrefix + "2Mi": resource.MustParse("10Mi")}, + }, + { + name: "pod-level resources, container-level resources, hugepage request/limit multiple page sizes", + podResources: v1.ResourceRequirements{ + Limits: v1.ResourceList{ + v1.ResourceHugePagesPrefix + "2Mi": resource.MustParse("10Mi"), + v1.ResourceHugePagesPrefix + "1Gi": resource.MustParse("2Gi"), + }, + Requests: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("1"), + v1.ResourceHugePagesPrefix + "2Mi": resource.MustParse("10Mi"), + v1.ResourceHugePagesPrefix + "1Gi": resource.MustParse("2Gi"), + }, + }, + containers: []v1.Container{ + { + Resources: v1.ResourceRequirements{ + Limits: v1.ResourceList{ + v1.ResourceHugePagesPrefix + "1Gi": resource.MustParse("2Gi"), + }, + Requests: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("1"), + v1.ResourceHugePagesPrefix + "1Gi": resource.MustParse("2Gi"), + }, + }, + }, + }, + opts: PodResourcesOptions{SkipPodLevelResources: false}, + expectedRequests: v1.ResourceList{v1.ResourceCPU: resource.MustParse("1"), v1.ResourceHugePagesPrefix + "2Mi": resource.MustParse("10Mi"), v1.ResourceHugePagesPrefix + "1Gi": resource.MustParse("2Gi")}, + }, + { + name: "pod-level resources, container-level resources, hugepage request/limit multiple page sizes between pod-level and container-level", + podResources: v1.ResourceRequirements{ + Limits: v1.ResourceList{ + v1.ResourceHugePagesPrefix + "2Mi": resource.MustParse("10Mi"), + }, + Requests: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("1"), + v1.ResourceHugePagesPrefix + "2Mi": resource.MustParse("10Mi"), + }, + }, + containers: []v1.Container{ + { + Resources: v1.ResourceRequirements{ + Limits: v1.ResourceList{ + v1.ResourceHugePagesPrefix + "1Gi": resource.MustParse("1Gi"), + }, + Requests: v1.ResourceList{ + v1.ResourceMemory: resource.MustParse("4Mi"), + v1.ResourceHugePagesPrefix + "1Gi": resource.MustParse("1Gi"), + }, + }, + }, + }, + opts: PodResourcesOptions{SkipPodLevelResources: false}, + expectedRequests: v1.ResourceList{v1.ResourceCPU: resource.MustParse("1"), v1.ResourceMemory: resource.MustParse("4Mi"), v1.ResourceHugePagesPrefix + "2Mi": resource.MustParse("10Mi"), v1.ResourceHugePagesPrefix + "1Gi": resource.MustParse("1Gi")}, + }, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { @@ -1721,6 +1833,47 @@ func TestPodLevelResourceRequests(t *testing.T) { } } +func TestIsSupportedPodLevelResource(t *testing.T) { + testCases := []struct { + name string + resource v1.ResourceName + expected bool + }{ + { + name: v1.ResourceCPU.String(), + resource: v1.ResourceCPU, + expected: true, + }, + { + name: v1.ResourceMemory.String(), + resource: v1.ResourceMemory, + expected: true, + }, + { + name: v1.ResourceEphemeralStorage.String(), + resource: v1.ResourceEphemeralStorage, + expected: false, + }, + { + name: v1.ResourceHugePagesPrefix + "2Mi", + resource: v1.ResourceHugePagesPrefix + "2Mi", + expected: true, + }, + { + name: v1.ResourceHugePagesPrefix + "1Gi", + resource: v1.ResourceHugePagesPrefix + "1Gi", + expected: true, + }, + } + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + if got := IsSupportedPodLevelResource(tc.resource); got != tc.expected { + t.Errorf("Supported pod level resource %s: got=%t, want=%t", tc.resource.String(), got, tc.expected) + } + }) + } +} + func TestAggregateContainerRequestsAndLimits(t *testing.T) { restartAlways := v1.ContainerRestartPolicyAlways cases := []struct {