diff --git a/pkg/api/v1/resource/helpers_test.go b/pkg/api/v1/resource/helpers_test.go index 8591d8d91f5..e7e92fdc980 100644 --- a/pkg/api/v1/resource/helpers_test.go +++ b/pkg/api/v1/resource/helpers_test.go @@ -23,7 +23,6 @@ import ( "github.com/stretchr/testify/require" v1 "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/api/equality" "k8s.io/apimachinery/pkg/api/resource" ) @@ -268,264 +267,6 @@ func TestExtractResourceValue(t *testing.T) { } } -func TestPodRequestsAndLimits(t *testing.T) { - cases := []struct { - pod *v1.Pod - cName string - expectedRequests v1.ResourceList - expectedLimits v1.ResourceList - }{ - { - cName: "just-limit-no-overhead", - pod: getPod("foo", podResources{cpuLimit: "9"}), - expectedRequests: v1.ResourceList{}, - expectedLimits: v1.ResourceList{ - v1.ResourceName(v1.ResourceCPU): resource.MustParse("9"), - }, - }, - { - cName: "just-overhead", - pod: getPod("foo", podResources{cpuOverhead: "5", memoryOverhead: "5"}), - expectedRequests: v1.ResourceList{ - v1.ResourceName(v1.ResourceCPU): resource.MustParse("5"), - v1.ResourceName(v1.ResourceMemory): resource.MustParse("5"), - }, - expectedLimits: v1.ResourceList{}, - }, - { - cName: "req-and-overhead", - pod: getPod("foo", podResources{cpuRequest: "1", memoryRequest: "10", cpuOverhead: "5", memoryOverhead: "5"}), - expectedRequests: v1.ResourceList{ - v1.ResourceName(v1.ResourceCPU): resource.MustParse("6"), - v1.ResourceName(v1.ResourceMemory): resource.MustParse("15"), - }, - expectedLimits: v1.ResourceList{}, - }, - { - cName: "all-req-lim-and-overhead", - pod: getPod("foo", podResources{cpuRequest: "1", cpuLimit: "2", memoryRequest: "10", memoryLimit: "12", cpuOverhead: "5", memoryOverhead: "5"}), - expectedRequests: v1.ResourceList{ - v1.ResourceName(v1.ResourceCPU): resource.MustParse("6"), - v1.ResourceName(v1.ResourceMemory): resource.MustParse("15"), - }, - expectedLimits: v1.ResourceList{ - v1.ResourceName(v1.ResourceCPU): resource.MustParse("7"), - v1.ResourceName(v1.ResourceMemory): resource.MustParse("17"), - }, - }, - { - cName: "req-some-lim-and-overhead", - pod: getPod("foo", podResources{cpuRequest: "1", cpuLimit: "2", memoryRequest: "10", cpuOverhead: "5", memoryOverhead: "5"}), - expectedRequests: v1.ResourceList{ - v1.ResourceName(v1.ResourceCPU): resource.MustParse("6"), - v1.ResourceName(v1.ResourceMemory): resource.MustParse("15"), - }, - expectedLimits: v1.ResourceList{ - v1.ResourceName(v1.ResourceCPU): resource.MustParse("7"), - }, - }, - } - for idx, tc := range cases { - resRequests := PodRequests(tc.pod, PodResourcesOptions{}) - resLimits := PodLimits(tc.pod, PodResourcesOptions{}) - - if !equality.Semantic.DeepEqual(tc.expectedRequests, resRequests) { - t.Errorf("test case failure[%d]: %v, requests:\n expected:\t%v\ngot\t\t%v", idx, tc.cName, tc.expectedRequests, resRequests) - } - - if !equality.Semantic.DeepEqual(tc.expectedLimits, resLimits) { - t.Errorf("test case failure[%d]: %v, limits:\n expected:\t%v\ngot\t\t%v", idx, tc.cName, tc.expectedLimits, resLimits) - } - } -} - -func TestPodRequestsAndLimitsWithoutOverhead(t *testing.T) { - cases := []struct { - pod *v1.Pod - name string - expectedRequests v1.ResourceList - expectedLimits v1.ResourceList - }{ - { - name: "two container no overhead - should just be sum of containers", - pod: &v1.Pod{ - Spec: v1.PodSpec{ - Containers: []v1.Container{ - { - Name: "foobar", - Resources: v1.ResourceRequirements{ - Requests: v1.ResourceList{ - v1.ResourceName(v1.ResourceCPU): resource.MustParse("1"), - v1.ResourceName(v1.ResourceMemory): resource.MustParse("5"), - }, - Limits: v1.ResourceList{ - v1.ResourceName(v1.ResourceCPU): resource.MustParse("2"), - v1.ResourceName(v1.ResourceMemory): resource.MustParse("10"), - }, - }, - }, - { - Name: "foobar2", - Resources: v1.ResourceRequirements{ - Requests: v1.ResourceList{ - v1.ResourceName(v1.ResourceCPU): resource.MustParse("4"), - v1.ResourceName(v1.ResourceMemory): resource.MustParse("12"), - }, - Limits: v1.ResourceList{ - v1.ResourceName(v1.ResourceCPU): resource.MustParse("8"), - v1.ResourceName(v1.ResourceMemory): resource.MustParse("24"), - }, - }, - }, - }, - }, - }, - expectedRequests: v1.ResourceList{ - v1.ResourceName(v1.ResourceCPU): resource.MustParse("5"), - v1.ResourceName(v1.ResourceMemory): resource.MustParse("17"), - }, - expectedLimits: v1.ResourceList{ - v1.ResourceName(v1.ResourceCPU): resource.MustParse("10"), - v1.ResourceName(v1.ResourceMemory): resource.MustParse("34"), - }, - }, - { - name: "two container with overhead - shouldn't consider overhead", - pod: &v1.Pod{ - Spec: v1.PodSpec{ - Overhead: v1.ResourceList{ - v1.ResourceName(v1.ResourceCPU): resource.MustParse("3"), - v1.ResourceName(v1.ResourceMemory): resource.MustParse("8"), - }, - Containers: []v1.Container{ - { - Name: "foobar", - Resources: v1.ResourceRequirements{ - Requests: v1.ResourceList{ - v1.ResourceName(v1.ResourceCPU): resource.MustParse("1"), - v1.ResourceName(v1.ResourceMemory): resource.MustParse("5"), - }, - Limits: v1.ResourceList{ - v1.ResourceName(v1.ResourceCPU): resource.MustParse("2"), - v1.ResourceName(v1.ResourceMemory): resource.MustParse("10"), - }, - }, - }, - { - Name: "foobar2", - Resources: v1.ResourceRequirements{ - Requests: v1.ResourceList{ - v1.ResourceName(v1.ResourceCPU): resource.MustParse("4"), - v1.ResourceName(v1.ResourceMemory): resource.MustParse("12"), - }, - Limits: v1.ResourceList{ - v1.ResourceName(v1.ResourceCPU): resource.MustParse("8"), - v1.ResourceName(v1.ResourceMemory): resource.MustParse("24"), - }, - }, - }, - }, - }, - }, - expectedRequests: v1.ResourceList{ - v1.ResourceName(v1.ResourceCPU): resource.MustParse("5"), - v1.ResourceName(v1.ResourceMemory): resource.MustParse("17"), - }, - expectedLimits: v1.ResourceList{ - v1.ResourceName(v1.ResourceCPU): resource.MustParse("10"), - v1.ResourceName(v1.ResourceMemory): resource.MustParse("34"), - }, - }, - { - name: "two container with overhead, massive init - should just be the largest init", - pod: &v1.Pod{ - Spec: v1.PodSpec{ - Overhead: v1.ResourceList{ - v1.ResourceName(v1.ResourceCPU): resource.MustParse("3"), - v1.ResourceName(v1.ResourceMemory): resource.MustParse("8"), - }, - Containers: []v1.Container{ - { - Name: "foobar", - Resources: v1.ResourceRequirements{ - Requests: v1.ResourceList{ - v1.ResourceName(v1.ResourceCPU): resource.MustParse("1"), - v1.ResourceName(v1.ResourceMemory): resource.MustParse("5"), - }, - Limits: v1.ResourceList{ - v1.ResourceName(v1.ResourceCPU): resource.MustParse("2"), - v1.ResourceName(v1.ResourceMemory): resource.MustParse("10"), - }, - }, - }, - { - Name: "foobar2", - Resources: v1.ResourceRequirements{ - Requests: v1.ResourceList{ - v1.ResourceName(v1.ResourceCPU): resource.MustParse("4"), - v1.ResourceName(v1.ResourceMemory): resource.MustParse("12"), - }, - Limits: v1.ResourceList{ - v1.ResourceName(v1.ResourceCPU): resource.MustParse("8"), - v1.ResourceName(v1.ResourceMemory): resource.MustParse("24"), - }, - }, - }, - }, - InitContainers: []v1.Container{ - { - Name: "small-init", - Resources: v1.ResourceRequirements{ - Requests: v1.ResourceList{ - v1.ResourceName(v1.ResourceCPU): resource.MustParse("1"), - v1.ResourceName(v1.ResourceMemory): resource.MustParse("5"), - }, - Limits: v1.ResourceList{ - v1.ResourceName(v1.ResourceCPU): resource.MustParse("1"), - v1.ResourceName(v1.ResourceMemory): resource.MustParse("5"), - }, - }, - }, - { - Name: "big-init", - Resources: v1.ResourceRequirements{ - Requests: v1.ResourceList{ - v1.ResourceName(v1.ResourceCPU): resource.MustParse("40"), - v1.ResourceName(v1.ResourceMemory): resource.MustParse("120"), - }, - Limits: v1.ResourceList{ - v1.ResourceName(v1.ResourceCPU): resource.MustParse("80"), - v1.ResourceName(v1.ResourceMemory): resource.MustParse("240"), - }, - }, - }, - }, - }, - }, - expectedRequests: v1.ResourceList{ - v1.ResourceName(v1.ResourceCPU): resource.MustParse("40"), - v1.ResourceName(v1.ResourceMemory): resource.MustParse("120"), - }, - expectedLimits: v1.ResourceList{ - v1.ResourceName(v1.ResourceCPU): resource.MustParse("80"), - v1.ResourceName(v1.ResourceMemory): resource.MustParse("240"), - }, - }, - } - for idx, tc := range cases { - resRequests := PodRequests(tc.pod, PodResourcesOptions{ExcludeOverhead: true}) - resLimits := PodLimits(tc.pod, PodResourcesOptions{ExcludeOverhead: true}) - - if !equality.Semantic.DeepEqual(tc.expectedRequests, resRequests) { - t.Errorf("test case failure[%d]: %v, requests:\n expected:\t%v\ngot\t\t%v", idx, tc.name, tc.expectedRequests, resRequests) - } - - if !equality.Semantic.DeepEqual(tc.expectedLimits, resLimits) { - t.Errorf("test case failure[%d]: %v, limits:\n expected:\t%v\ngot\t\t%v", idx, tc.name, tc.expectedLimits, resLimits) - } - } -} - type podResources struct { cpuRequest, cpuLimit, memoryRequest, memoryLimit, cpuOverhead, memoryOverhead string } @@ -575,873 +316,3 @@ func getPod(cname string, resources podResources) *v1.Pod { }, } } - -func TestPodResourceRequests(t *testing.T) { - restartAlways := v1.ContainerRestartPolicyAlways - testCases := []struct { - description string - options PodResourcesOptions - overhead v1.ResourceList - podResizeStatus v1.PodResizeStatus - initContainers []v1.Container - containers []v1.Container - containerStatus []v1.ContainerStatus - expectedRequests v1.ResourceList - }{ - { - description: "nil options, larger init container", - expectedRequests: v1.ResourceList{ - v1.ResourceCPU: resource.MustParse("4"), - }, - initContainers: []v1.Container{ - { - Resources: v1.ResourceRequirements{ - Requests: v1.ResourceList{ - v1.ResourceCPU: resource.MustParse("4"), - }, - }, - }, - }, - containers: []v1.Container{ - { - Resources: v1.ResourceRequirements{ - Requests: v1.ResourceList{ - v1.ResourceCPU: resource.MustParse("1"), - }, - }, - }, - }, - }, - { - description: "nil options, larger containers", - expectedRequests: v1.ResourceList{ - v1.ResourceCPU: resource.MustParse("5"), - }, - initContainers: []v1.Container{ - { - Resources: v1.ResourceRequirements{ - Requests: v1.ResourceList{ - v1.ResourceCPU: resource.MustParse("2"), - }, - }, - }, - }, - containers: []v1.Container{ - { - Resources: v1.ResourceRequirements{ - Requests: v1.ResourceList{ - v1.ResourceCPU: resource.MustParse("2"), - }, - }, - }, - { - Resources: v1.ResourceRequirements{ - Requests: v1.ResourceList{ - v1.ResourceCPU: resource.MustParse("3"), - }, - }, - }, - }, - }, - { - description: "pod overhead excluded", - expectedRequests: v1.ResourceList{ - v1.ResourceCPU: resource.MustParse("5"), - }, - options: PodResourcesOptions{ - ExcludeOverhead: true, - }, - overhead: v1.ResourceList{ - v1.ResourceCPU: resource.MustParse("1"), - }, - initContainers: []v1.Container{ - { - Resources: v1.ResourceRequirements{ - Requests: v1.ResourceList{ - v1.ResourceCPU: resource.MustParse("2"), - }, - }, - }, - }, - containers: []v1.Container{ - { - Resources: v1.ResourceRequirements{ - Requests: v1.ResourceList{ - v1.ResourceCPU: resource.MustParse("2"), - }, - }, - }, - { - Resources: v1.ResourceRequirements{ - Requests: v1.ResourceList{ - v1.ResourceCPU: resource.MustParse("3"), - }, - }, - }, - }, - }, - { - description: "pod overhead included", - expectedRequests: v1.ResourceList{ - v1.ResourceCPU: resource.MustParse("6"), - v1.ResourceMemory: resource.MustParse("1Gi"), - }, - overhead: v1.ResourceList{ - v1.ResourceCPU: resource.MustParse("1"), - v1.ResourceMemory: resource.MustParse("1Gi"), - }, - initContainers: []v1.Container{ - { - Resources: v1.ResourceRequirements{ - Requests: v1.ResourceList{ - v1.ResourceCPU: resource.MustParse("2"), - }, - Limits: v1.ResourceList{ - v1.ResourceCPU: resource.MustParse("2"), - }, - }, - }, - }, - containers: []v1.Container{ - { - Resources: v1.ResourceRequirements{ - Requests: v1.ResourceList{ - v1.ResourceCPU: resource.MustParse("2"), - }, - }, - }, - { - Resources: v1.ResourceRequirements{ - Requests: v1.ResourceList{ - v1.ResourceCPU: resource.MustParse("3"), - }, - }, - }, - }, - }, - { - description: "resized, infeasible", - expectedRequests: v1.ResourceList{ - v1.ResourceCPU: resource.MustParse("2"), - }, - podResizeStatus: v1.PodResizeStatusInfeasible, - options: PodResourcesOptions{InPlacePodVerticalScalingEnabled: true}, - containers: []v1.Container{ - { - Name: "container-1", - Resources: v1.ResourceRequirements{ - Requests: v1.ResourceList{ - v1.ResourceCPU: resource.MustParse("4"), - }, - }, - }, - }, - containerStatus: []v1.ContainerStatus{ - { - Name: "container-1", - AllocatedResources: v1.ResourceList{ - v1.ResourceCPU: resource.MustParse("2"), - }, - }, - }, - }, - { - description: "resized, no resize status", - expectedRequests: v1.ResourceList{ - v1.ResourceCPU: resource.MustParse("4"), - }, - options: PodResourcesOptions{InPlacePodVerticalScalingEnabled: true}, - containers: []v1.Container{ - { - Name: "container-1", - Resources: v1.ResourceRequirements{ - Requests: v1.ResourceList{ - v1.ResourceCPU: resource.MustParse("4"), - }, - }, - }, - }, - containerStatus: []v1.ContainerStatus{ - { - Name: "container-1", - AllocatedResources: v1.ResourceList{ - v1.ResourceCPU: resource.MustParse("2"), - }, - }, - }, - }, - { - description: "resized, infeasible, feature gate disabled", - expectedRequests: v1.ResourceList{ - v1.ResourceCPU: resource.MustParse("4"), - }, - podResizeStatus: v1.PodResizeStatusInfeasible, - options: PodResourcesOptions{InPlacePodVerticalScalingEnabled: false}, - containers: []v1.Container{ - { - Name: "container-1", - Resources: v1.ResourceRequirements{ - Requests: v1.ResourceList{ - v1.ResourceCPU: resource.MustParse("4"), - }, - }, - }, - }, - containerStatus: []v1.ContainerStatus{ - { - Name: "container-1", - AllocatedResources: v1.ResourceList{ - v1.ResourceCPU: resource.MustParse("2"), - }, - }, - }, - }, - { - description: "restartable init container", - expectedRequests: v1.ResourceList{ - // restartable init + regular container - v1.ResourceCPU: resource.MustParse("2"), - }, - initContainers: []v1.Container{ - { - Name: "restartable-init-1", - RestartPolicy: &restartAlways, - Resources: v1.ResourceRequirements{ - Requests: v1.ResourceList{ - v1.ResourceCPU: resource.MustParse("1"), - }, - }, - }, - }, - containers: []v1.Container{ - { - Name: "container-1", - Resources: v1.ResourceRequirements{ - Requests: v1.ResourceList{ - v1.ResourceCPU: resource.MustParse("1"), - }, - }, - }, - }, - }, - { - description: "multiple restartable init containers", - expectedRequests: v1.ResourceList{ - // max(5, restartable init containers(3+2+1) + regular(1)) = 7 - v1.ResourceCPU: resource.MustParse("7"), - }, - initContainers: []v1.Container{ - { - Name: "init-1", - Resources: v1.ResourceRequirements{ - Requests: v1.ResourceList{ - v1.ResourceCPU: resource.MustParse("5"), - }, - }, - }, - { - Name: "restartable-init-1", - RestartPolicy: &restartAlways, - Resources: v1.ResourceRequirements{ - Requests: v1.ResourceList{ - v1.ResourceCPU: resource.MustParse("1"), - }, - }, - }, - { - Name: "restartable-init-2", - RestartPolicy: &restartAlways, - Resources: v1.ResourceRequirements{ - Requests: v1.ResourceList{ - v1.ResourceCPU: resource.MustParse("2"), - }, - }, - }, - { - Name: "restartable-init-3", - RestartPolicy: &restartAlways, - Resources: v1.ResourceRequirements{ - Requests: v1.ResourceList{ - v1.ResourceCPU: resource.MustParse("3"), - }, - }, - }, - }, - containers: []v1.Container{ - { - Name: "container-1", - Resources: v1.ResourceRequirements{ - Requests: v1.ResourceList{ - v1.ResourceCPU: resource.MustParse("1"), - }, - }, - }, - }, - }, - { - description: "multiple restartable and regular init containers", - expectedRequests: v1.ResourceList{ - // init-2 requires 5 + the previously running restartable init - // containers(1+2) = 8, the restartable init container that starts - // after it doesn't count - v1.ResourceCPU: resource.MustParse("8"), - }, - initContainers: []v1.Container{ - { - Name: "init-1", - Resources: v1.ResourceRequirements{ - Requests: v1.ResourceList{ - v1.ResourceCPU: resource.MustParse("5"), - }, - }, - }, - { - Name: "restartable-init-1", - RestartPolicy: &restartAlways, - Resources: v1.ResourceRequirements{ - Requests: v1.ResourceList{ - v1.ResourceCPU: resource.MustParse("1"), - }, - }, - }, - { - Name: "restartable-init-2", - RestartPolicy: &restartAlways, - Resources: v1.ResourceRequirements{ - Requests: v1.ResourceList{ - v1.ResourceCPU: resource.MustParse("2"), - }, - }, - }, - { - Name: "init-2", - Resources: v1.ResourceRequirements{ - Requests: v1.ResourceList{ - v1.ResourceCPU: resource.MustParse("5"), - }, - }, - }, - { - Name: "restartable-init-3", - RestartPolicy: &restartAlways, - Resources: v1.ResourceRequirements{ - Requests: v1.ResourceList{ - v1.ResourceCPU: resource.MustParse("3"), - }, - }, - }, - }, - containers: []v1.Container{ - { - Name: "container-1", - Resources: v1.ResourceRequirements{ - Requests: v1.ResourceList{ - v1.ResourceCPU: resource.MustParse("1"), - }, - }, - }, - }, - }, - { - description: "restartable-init, init and regular", - expectedRequests: v1.ResourceList{ - v1.ResourceCPU: resource.MustParse("210"), - }, - initContainers: []v1.Container{ - { - Name: "restartable-init-1", - RestartPolicy: &restartAlways, - Resources: v1.ResourceRequirements{ - Requests: v1.ResourceList{ - v1.ResourceCPU: resource.MustParse("10"), - }, - }, - }, - { - Name: "init-1", - Resources: v1.ResourceRequirements{ - Requests: v1.ResourceList{ - v1.ResourceCPU: resource.MustParse("200"), - }, - }, - }, - }, - containers: []v1.Container{ - { - Name: "container-1", - Resources: v1.ResourceRequirements{ - Requests: v1.ResourceList{ - v1.ResourceCPU: resource.MustParse("100"), - }, - }, - }, - }, - }, - } - for _, tc := range testCases { - t.Run(tc.description, func(t *testing.T) { - p := &v1.Pod{ - Spec: v1.PodSpec{ - Containers: tc.containers, - InitContainers: tc.initContainers, - Overhead: tc.overhead, - }, - Status: v1.PodStatus{ - ContainerStatuses: tc.containerStatus, - Resize: tc.podResizeStatus, - }, - } - request := PodRequests(p, tc.options) - if !resourcesEqual(tc.expectedRequests, request) { - t.Errorf("[%s] expected requests = %v, got %v", tc.description, tc.expectedRequests, request) - } - }) - } -} - -func TestPodResourceRequestsReuse(t *testing.T) { - expectedRequests := v1.ResourceList{ - v1.ResourceCPU: resource.MustParse("1"), - } - p := &v1.Pod{ - Spec: v1.PodSpec{ - Containers: []v1.Container{ - { - Resources: v1.ResourceRequirements{ - Requests: expectedRequests, - }, - }, - }, - }, - } - - opts := PodResourcesOptions{ - Reuse: v1.ResourceList{ - v1.ResourceCPU: resource.MustParse("25"), - }, - } - requests := PodRequests(p, opts) - - if !resourcesEqual(expectedRequests, requests) { - t.Errorf("expected requests = %v, got %v", expectedRequests, requests) - } - - // should re-use the maps we passed in - if !resourcesEqual(expectedRequests, opts.Reuse) { - t.Errorf("expected to re-use the requests") - } -} - -func TestPodResourceLimits(t *testing.T) { - restartAlways := v1.ContainerRestartPolicyAlways - testCases := []struct { - description string - options PodResourcesOptions - overhead v1.ResourceList - initContainers []v1.Container - containers []v1.Container - expectedLimits v1.ResourceList - }{ - { - description: "nil options, larger init container", - expectedLimits: v1.ResourceList{ - v1.ResourceCPU: resource.MustParse("4"), - }, - initContainers: []v1.Container{ - { - Resources: v1.ResourceRequirements{ - Limits: v1.ResourceList{ - v1.ResourceCPU: resource.MustParse("4"), - }, - }, - }, - }, - containers: []v1.Container{ - { - Resources: v1.ResourceRequirements{ - Limits: v1.ResourceList{ - v1.ResourceCPU: resource.MustParse("1"), - }, - }, - }, - }, - }, - { - description: "nil options, larger containers", - expectedLimits: v1.ResourceList{ - v1.ResourceCPU: resource.MustParse("5"), - }, - initContainers: []v1.Container{ - { - Resources: v1.ResourceRequirements{ - Limits: v1.ResourceList{ - v1.ResourceCPU: resource.MustParse("2"), - }, - }, - }, - }, - containers: []v1.Container{ - { - Resources: v1.ResourceRequirements{ - Limits: v1.ResourceList{ - v1.ResourceCPU: resource.MustParse("2"), - }, - }, - }, - { - Resources: v1.ResourceRequirements{ - Limits: v1.ResourceList{ - v1.ResourceCPU: resource.MustParse("3"), - }, - }, - }, - }, - }, - { - description: "pod overhead excluded", - expectedLimits: v1.ResourceList{ - v1.ResourceCPU: resource.MustParse("5"), - }, - options: PodResourcesOptions{ - ExcludeOverhead: true, - }, - overhead: v1.ResourceList{ - v1.ResourceCPU: resource.MustParse("1"), - }, - initContainers: []v1.Container{ - { - Resources: v1.ResourceRequirements{ - Limits: v1.ResourceList{ - v1.ResourceCPU: resource.MustParse("2"), - }, - }, - }, - }, - containers: []v1.Container{ - { - Resources: v1.ResourceRequirements{ - Limits: v1.ResourceList{ - v1.ResourceCPU: resource.MustParse("2"), - }, - }, - }, - { - Resources: v1.ResourceRequirements{ - Limits: v1.ResourceList{ - v1.ResourceCPU: resource.MustParse("3"), - }, - }, - }, - }, - }, - { - description: "pod overhead included", - overhead: v1.ResourceList{ - v1.ResourceCPU: resource.MustParse("1"), - v1.ResourceMemory: resource.MustParse("1Gi"), - }, - expectedLimits: v1.ResourceList{ - v1.ResourceCPU: resource.MustParse("6"), - // overhead is only added to non-zero limits, so there will be no expected memory limit - }, - initContainers: []v1.Container{ - { - Resources: v1.ResourceRequirements{ - Limits: v1.ResourceList{ - v1.ResourceCPU: resource.MustParse("2"), - }, - }, - }, - }, - containers: []v1.Container{ - { - Resources: v1.ResourceRequirements{ - Limits: v1.ResourceList{ - v1.ResourceCPU: resource.MustParse("2"), - }, - }, - }, - { - Resources: v1.ResourceRequirements{ - Limits: v1.ResourceList{ - v1.ResourceCPU: resource.MustParse("3"), - }, - }, - }, - }, - }, - { - description: "no limited containers should result in no limits for the pod", - expectedLimits: v1.ResourceList{}, - initContainers: []v1.Container{}, - containers: []v1.Container{ - { - // Unlimited container - }, - }, - }, - { - description: "one limited and one unlimited container should result in the limited container's limits for the pod", - expectedLimits: v1.ResourceList{ - v1.ResourceCPU: resource.MustParse("2"), - v1.ResourceMemory: resource.MustParse("2Gi"), - }, - initContainers: []v1.Container{}, - containers: []v1.Container{ - { - Resources: v1.ResourceRequirements{ - Limits: v1.ResourceList{ - v1.ResourceCPU: resource.MustParse("2"), - v1.ResourceMemory: resource.MustParse("2Gi"), - }, - }, - }, - { - // Unlimited container - }, - }, - }, - { - description: "one limited and one unlimited init container should result in the limited init container's limits for the pod", - expectedLimits: v1.ResourceList{ - v1.ResourceCPU: resource.MustParse("2"), - v1.ResourceMemory: resource.MustParse("2Gi"), - }, - initContainers: []v1.Container{ - { - Resources: v1.ResourceRequirements{ - Limits: v1.ResourceList{ - v1.ResourceCPU: resource.MustParse("2"), - v1.ResourceMemory: resource.MustParse("2Gi"), - }, - }, - }, - { - // Unlimited init container - }, - }, - containers: []v1.Container{ - { - Resources: v1.ResourceRequirements{ - Limits: v1.ResourceList{ - v1.ResourceCPU: resource.MustParse("1"), - v1.ResourceMemory: resource.MustParse("1Gi"), - }, - }, - }, - }, - }, - { - description: "restartable init container", - expectedLimits: v1.ResourceList{ - // restartable init + regular container - v1.ResourceCPU: resource.MustParse("2"), - }, - initContainers: []v1.Container{ - { - Name: "restartable-init-1", - RestartPolicy: &restartAlways, - Resources: v1.ResourceRequirements{ - Limits: v1.ResourceList{ - v1.ResourceCPU: resource.MustParse("1"), - }, - }, - }, - }, - containers: []v1.Container{ - { - Name: "container-1", - Resources: v1.ResourceRequirements{ - Limits: v1.ResourceList{ - v1.ResourceCPU: resource.MustParse("1"), - }, - }, - }, - }, - }, - { - description: "multiple restartable init containers", - expectedLimits: v1.ResourceList{ - // max(5, restartable init containers(3+2+1) + regular(1)) = 7 - v1.ResourceCPU: resource.MustParse("7"), - }, - initContainers: []v1.Container{ - { - Name: "init-1", - Resources: v1.ResourceRequirements{ - Limits: v1.ResourceList{ - v1.ResourceCPU: resource.MustParse("5"), - }, - }, - }, - { - Name: "restartable-init-1", - RestartPolicy: &restartAlways, - Resources: v1.ResourceRequirements{ - Limits: v1.ResourceList{ - v1.ResourceCPU: resource.MustParse("1"), - }, - }, - }, - { - Name: "restartable-init-2", - RestartPolicy: &restartAlways, - Resources: v1.ResourceRequirements{ - Limits: v1.ResourceList{ - v1.ResourceCPU: resource.MustParse("2"), - }, - }, - }, - { - Name: "restartable-init-3", - RestartPolicy: &restartAlways, - Resources: v1.ResourceRequirements{ - Limits: v1.ResourceList{ - v1.ResourceCPU: resource.MustParse("3"), - }, - }, - }, - }, - containers: []v1.Container{ - { - Name: "container-1", - Resources: v1.ResourceRequirements{ - Limits: v1.ResourceList{ - v1.ResourceCPU: resource.MustParse("1"), - }, - }, - }, - }, - }, - { - description: "multiple restartable and regular init containers", - expectedLimits: v1.ResourceList{ - // init-2 requires 5 + the previously running restartable init - // containers(1+2) = 8, the restartable init container that starts - // after it doesn't count - v1.ResourceCPU: resource.MustParse("8"), - }, - initContainers: []v1.Container{ - { - Name: "init-1", - Resources: v1.ResourceRequirements{ - Limits: v1.ResourceList{ - v1.ResourceCPU: resource.MustParse("5"), - }, - }, - }, - { - Name: "restartable-init-1", - RestartPolicy: &restartAlways, - Resources: v1.ResourceRequirements{ - Limits: v1.ResourceList{ - v1.ResourceCPU: resource.MustParse("1"), - }, - }, - }, - { - Name: "restartable-init-2", - RestartPolicy: &restartAlways, - Resources: v1.ResourceRequirements{ - Limits: v1.ResourceList{ - v1.ResourceCPU: resource.MustParse("2"), - }, - }, - }, - { - Name: "init-2", - Resources: v1.ResourceRequirements{ - Limits: v1.ResourceList{ - v1.ResourceCPU: resource.MustParse("5"), - }, - }, - }, - { - Name: "restartable-init-3", - RestartPolicy: &restartAlways, - Resources: v1.ResourceRequirements{ - Limits: v1.ResourceList{ - v1.ResourceCPU: resource.MustParse("3"), - }, - }, - }, - }, - containers: []v1.Container{ - { - Name: "container-1", - Resources: v1.ResourceRequirements{ - Limits: v1.ResourceList{ - v1.ResourceCPU: resource.MustParse("1"), - }, - }, - }, - }, - }, - { - description: "restartable-init, init and regular", - expectedLimits: v1.ResourceList{ - v1.ResourceCPU: resource.MustParse("210"), - }, - initContainers: []v1.Container{ - { - Name: "restartable-init-1", - RestartPolicy: &restartAlways, - Resources: v1.ResourceRequirements{ - Limits: v1.ResourceList{ - v1.ResourceCPU: resource.MustParse("10"), - }, - }, - }, - { - Name: "init-1", - Resources: v1.ResourceRequirements{ - Limits: v1.ResourceList{ - v1.ResourceCPU: resource.MustParse("200"), - }, - }, - }, - }, - containers: []v1.Container{ - { - Name: "container-1", - Resources: v1.ResourceRequirements{ - Limits: v1.ResourceList{ - v1.ResourceCPU: resource.MustParse("100"), - }, - }, - }, - }, - }, - } - for _, tc := range testCases { - t.Run(tc.description, func(t *testing.T) { - p := &v1.Pod{ - Spec: v1.PodSpec{ - Containers: tc.containers, - InitContainers: tc.initContainers, - Overhead: tc.overhead, - }, - } - limits := PodLimits(p, tc.options) - if !resourcesEqual(tc.expectedLimits, limits) { - t.Errorf("[%s] expected limits = %v, got %v", tc.description, tc.expectedLimits, limits) - } - }) - } -} - -func resourcesEqual(lhs, rhs v1.ResourceList) bool { - if len(lhs) != len(rhs) { - return false - } - for name, lhsv := range lhs { - rhsv, ok := rhs[name] - if !ok { - return false - } - if !lhsv.Equal(rhsv) { - return false - } - } - return true -} diff --git a/pkg/kubelet/cm/devicemanager/topology_hints.go b/pkg/kubelet/cm/devicemanager/topology_hints.go index 16630daad81..9b69f38eae1 100644 --- a/pkg/kubelet/cm/devicemanager/topology_hints.go +++ b/pkg/kubelet/cm/devicemanager/topology_hints.go @@ -17,12 +17,12 @@ limitations under the License. package devicemanager import ( - "k8s.io/api/core/v1" + v1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/util/sets" + "k8s.io/component-helpers/resource" "k8s.io/klog/v2" pluginapi "k8s.io/kubelet/pkg/apis/deviceplugin/v1beta1" - "k8s.io/kubernetes/pkg/api/v1/resource" "k8s.io/kubernetes/pkg/kubelet/cm/topologymanager" "k8s.io/kubernetes/pkg/kubelet/cm/topologymanager/bitmask" ) diff --git a/pkg/kubelet/cm/helpers_linux.go b/pkg/kubelet/cm/helpers_linux.go index 8a144e7a73c..f50a0122823 100644 --- a/pkg/kubelet/cm/helpers_linux.go +++ b/pkg/kubelet/cm/helpers_linux.go @@ -28,8 +28,7 @@ import ( "k8s.io/apimachinery/pkg/types" utilfeature "k8s.io/apiserver/pkg/util/feature" - podutil "k8s.io/kubernetes/pkg/api/v1/pod" - "k8s.io/kubernetes/pkg/api/v1/resource" + "k8s.io/component-helpers/resource" v1helper "k8s.io/kubernetes/pkg/apis/core/v1/helper" v1qos "k8s.io/kubernetes/pkg/apis/core/v1/helper/qos" kubefeatures "k8s.io/kubernetes/pkg/features" @@ -130,7 +129,7 @@ func ResourceConfigForPod(pod *v1.Pod, enforceCPULimits bool, cpuPeriod uint64, limits := resource.PodLimits(pod, resource.PodResourcesOptions{ InPlacePodVerticalScalingEnabled: inPlacePodVerticalScalingEnabled, - ContainerFn: func(res v1.ResourceList, containerType podutil.ContainerType) { + ContainerFn: func(res v1.ResourceList, containerType resource.ContainerType) { if res.Cpu().IsZero() { cpuLimitsDeclared = false } diff --git a/pkg/kubelet/cm/qos_container_manager_linux.go b/pkg/kubelet/cm/qos_container_manager_linux.go index a740acbbb49..441aa952b65 100644 --- a/pkg/kubelet/cm/qos_container_manager_linux.go +++ b/pkg/kubelet/cm/qos_container_manager_linux.go @@ -32,7 +32,7 @@ import ( libcontainercgroups "github.com/opencontainers/runc/libcontainer/cgroups" utilfeature "k8s.io/apiserver/pkg/util/feature" - "k8s.io/kubernetes/pkg/api/v1/resource" + "k8s.io/component-helpers/resource" v1qos "k8s.io/kubernetes/pkg/apis/core/v1/helper/qos" kubefeatures "k8s.io/kubernetes/pkg/features" ) diff --git a/pkg/kubelet/eviction/eviction_manager.go b/pkg/kubelet/eviction/eviction_manager.go index 63f599e843e..a3424476ab7 100644 --- a/pkg/kubelet/eviction/eviction_manager.go +++ b/pkg/kubelet/eviction/eviction_manager.go @@ -34,8 +34,8 @@ import ( statsapi "k8s.io/kubelet/pkg/apis/stats/v1alpha1" "k8s.io/utils/clock" + resourcehelper "k8s.io/component-helpers/resource" podutil "k8s.io/kubernetes/pkg/api/v1/pod" - resourcehelper "k8s.io/kubernetes/pkg/api/v1/resource" v1qos "k8s.io/kubernetes/pkg/apis/core/v1/helper/qos" "k8s.io/kubernetes/pkg/features" evictionapi "k8s.io/kubernetes/pkg/kubelet/eviction/api" diff --git a/pkg/kubelet/kuberuntime/kuberuntime_sandbox_linux.go b/pkg/kubelet/kuberuntime/kuberuntime_sandbox_linux.go index 96ae163c7e6..8415103737a 100644 --- a/pkg/kubelet/kuberuntime/kuberuntime_sandbox_linux.go +++ b/pkg/kubelet/kuberuntime/kuberuntime_sandbox_linux.go @@ -24,7 +24,7 @@ import ( "k8s.io/apimachinery/pkg/api/resource" runtimeapi "k8s.io/cri-api/pkg/apis/runtime/v1" - resourcehelper "k8s.io/kubernetes/pkg/api/v1/resource" + resourcehelper "k8s.io/component-helpers/resource" ) func (m *kubeGenericRuntimeManager) convertOverheadToLinuxResources(pod *v1.Pod) *runtimeapi.LinuxContainerResources { diff --git a/pkg/kubelet/volumemanager/cache/desired_state_of_world.go b/pkg/kubelet/volumemanager/cache/desired_state_of_world.go index 81a2240ad38..2262a6bf338 100644 --- a/pkg/kubelet/volumemanager/cache/desired_state_of_world.go +++ b/pkg/kubelet/volumemanager/cache/desired_state_of_world.go @@ -33,7 +33,7 @@ import ( "k8s.io/klog/v2" "k8s.io/kubernetes/pkg/volume/csi" - resourcehelper "k8s.io/kubernetes/pkg/api/v1/resource" + resourcehelper "k8s.io/component-helpers/resource" "k8s.io/kubernetes/pkg/features" "k8s.io/kubernetes/pkg/volume" "k8s.io/kubernetes/pkg/volume/util" diff --git a/pkg/quota/v1/evaluator/core/pods.go b/pkg/quota/v1/evaluator/core/pods.go index a201c58383e..e8a6096e395 100644 --- a/pkg/quota/v1/evaluator/core/pods.go +++ b/pkg/quota/v1/evaluator/core/pods.go @@ -33,7 +33,7 @@ import ( "k8s.io/apiserver/pkg/util/feature" "k8s.io/utils/clock" - resourcehelper "k8s.io/kubernetes/pkg/api/v1/resource" + resourcehelper "k8s.io/component-helpers/resource" api "k8s.io/kubernetes/pkg/apis/core" k8s_api_v1 "k8s.io/kubernetes/pkg/apis/core/v1" "k8s.io/kubernetes/pkg/apis/core/v1/helper" diff --git a/pkg/scheduler/framework/events.go b/pkg/scheduler/framework/events.go index 66c14056ff0..a2bb48d431f 100644 --- a/pkg/scheduler/framework/events.go +++ b/pkg/scheduler/framework/events.go @@ -20,8 +20,8 @@ import ( v1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/equality" utilfeature "k8s.io/apiserver/pkg/util/feature" + "k8s.io/component-helpers/resource" "k8s.io/dynamic-resource-allocation/resourceclaim" - "k8s.io/kubernetes/pkg/api/v1/resource" "k8s.io/kubernetes/pkg/features" ) diff --git a/pkg/scheduler/framework/plugins/noderesources/fit.go b/pkg/scheduler/framework/plugins/noderesources/fit.go index 7605001aa12..5421f5326e5 100644 --- a/pkg/scheduler/framework/plugins/noderesources/fit.go +++ b/pkg/scheduler/framework/plugins/noderesources/fit.go @@ -26,8 +26,8 @@ import ( v1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/util/sets" + "k8s.io/component-helpers/resource" "k8s.io/klog/v2" - "k8s.io/kubernetes/pkg/api/v1/resource" v1helper "k8s.io/kubernetes/pkg/apis/core/v1/helper" "k8s.io/kubernetes/pkg/scheduler/apis/config" "k8s.io/kubernetes/pkg/scheduler/apis/config/validation" diff --git a/pkg/scheduler/framework/plugins/noderesources/resource_allocation.go b/pkg/scheduler/framework/plugins/noderesources/resource_allocation.go index 863647bd0af..752fcc89f38 100644 --- a/pkg/scheduler/framework/plugins/noderesources/resource_allocation.go +++ b/pkg/scheduler/framework/plugins/noderesources/resource_allocation.go @@ -24,7 +24,7 @@ import ( utilfeature "k8s.io/apiserver/pkg/util/feature" "k8s.io/klog/v2" - resourcehelper "k8s.io/kubernetes/pkg/api/v1/resource" + resourcehelper "k8s.io/component-helpers/resource" "k8s.io/kubernetes/pkg/features" "k8s.io/kubernetes/pkg/scheduler/apis/config" "k8s.io/kubernetes/pkg/scheduler/framework" diff --git a/pkg/scheduler/framework/types.go b/pkg/scheduler/framework/types.go index 18142ea6faa..fb3e5ddeaca 100644 --- a/pkg/scheduler/framework/types.go +++ b/pkg/scheduler/framework/types.go @@ -33,7 +33,7 @@ import ( "k8s.io/klog/v2" "k8s.io/apimachinery/pkg/api/resource" - resourcehelper "k8s.io/kubernetes/pkg/api/v1/resource" + resourcehelper "k8s.io/component-helpers/resource" "k8s.io/kubernetes/pkg/features" schedutil "k8s.io/kubernetes/pkg/scheduler/util" ) diff --git a/pkg/scheduler/metrics/resources/resources.go b/pkg/scheduler/metrics/resources/resources.go index cdb38050694..ac2124e00d7 100644 --- a/pkg/scheduler/metrics/resources/resources.go +++ b/pkg/scheduler/metrics/resources/resources.go @@ -29,7 +29,7 @@ import ( corelisters "k8s.io/client-go/listers/core/v1" "k8s.io/component-base/metrics" - v1resource "k8s.io/kubernetes/pkg/api/v1/resource" + resourcehelper "k8s.io/component-helpers/resource" v1helper "k8s.io/kubernetes/pkg/apis/core/v1/helper" ) @@ -181,7 +181,7 @@ func recordMetricWithUnit( // total container resource requests and to the total container limits which have a // non-zero quantity. The caller may avoid allocations of resource lists by passing // a requests and limits list to the function, which will be cleared before use. -// This method is the same as v1resource.PodRequestsAndLimits but avoids allocating in several +// This method is the same as resourcehelper.PodRequestsAndLimits but avoids allocating in several // scenarios for efficiency. func podRequestsAndLimitsByLifecycle(pod *v1.Pod, reuseReqs, reuseLimits v1.ResourceList) (reqs, limits v1.ResourceList, terminal bool) { switch { @@ -196,7 +196,7 @@ func podRequestsAndLimitsByLifecycle(pod *v1.Pod, reuseReqs, reuseLimits v1.Reso return } - reqs = v1resource.PodRequests(pod, v1resource.PodResourcesOptions{Reuse: reuseReqs}) - limits = v1resource.PodLimits(pod, v1resource.PodResourcesOptions{Reuse: reuseLimits}) + reqs = resourcehelper.PodRequests(pod, resourcehelper.PodResourcesOptions{Reuse: reuseReqs}) + limits = resourcehelper.PodLimits(pod, resourcehelper.PodResourcesOptions{Reuse: reuseLimits}) return } diff --git a/staging/src/k8s.io/component-helpers/resource/OWNERS b/staging/src/k8s.io/component-helpers/resource/OWNERS index 3ac6e2a21b2..d238eb22d39 100644 --- a/staging/src/k8s.io/component-helpers/resource/OWNERS +++ b/staging/src/k8s.io/component-helpers/resource/OWNERS @@ -10,4 +10,4 @@ reviewers: labels: - sig/node - sig/scheduling - - kind/api-change \ No newline at end of file + - kind/api-change diff --git a/staging/src/k8s.io/component-helpers/resource/helpers_test.go b/staging/src/k8s.io/component-helpers/resource/helpers_test.go new file mode 100644 index 00000000000..9d993f48eb6 --- /dev/null +++ b/staging/src/k8s.io/component-helpers/resource/helpers_test.go @@ -0,0 +1,1188 @@ +/* +Copyright 2024 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package resource + +import ( + "testing" + + "github.com/google/go-cmp/cmp" + v1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/equality" + "k8s.io/apimachinery/pkg/api/resource" +) + +func TestPodRequestsAndLimits(t *testing.T) { + cases := []struct { + pod *v1.Pod + cName string + expectedRequests v1.ResourceList + expectedLimits v1.ResourceList + }{ + { + cName: "just-limit-no-overhead", + pod: getPod("foo", podResources{cpuLimit: "9"}), + expectedRequests: v1.ResourceList{}, + expectedLimits: v1.ResourceList{ + v1.ResourceName(v1.ResourceCPU): resource.MustParse("9"), + }, + }, + { + cName: "just-overhead", + pod: getPod("foo", podResources{cpuOverhead: "5", memoryOverhead: "5"}), + expectedRequests: v1.ResourceList{ + v1.ResourceName(v1.ResourceCPU): resource.MustParse("5"), + v1.ResourceName(v1.ResourceMemory): resource.MustParse("5"), + }, + expectedLimits: v1.ResourceList{}, + }, + { + cName: "req-and-overhead", + pod: getPod("foo", podResources{cpuRequest: "1", memoryRequest: "10", cpuOverhead: "5", memoryOverhead: "5"}), + expectedRequests: v1.ResourceList{ + v1.ResourceName(v1.ResourceCPU): resource.MustParse("6"), + v1.ResourceName(v1.ResourceMemory): resource.MustParse("15"), + }, + expectedLimits: v1.ResourceList{}, + }, + { + cName: "all-req-lim-and-overhead", + pod: getPod("foo", podResources{cpuRequest: "1", cpuLimit: "2", memoryRequest: "10", memoryLimit: "12", cpuOverhead: "5", memoryOverhead: "5"}), + expectedRequests: v1.ResourceList{ + v1.ResourceName(v1.ResourceCPU): resource.MustParse("6"), + v1.ResourceName(v1.ResourceMemory): resource.MustParse("15"), + }, + expectedLimits: v1.ResourceList{ + v1.ResourceName(v1.ResourceCPU): resource.MustParse("7"), + v1.ResourceName(v1.ResourceMemory): resource.MustParse("17"), + }, + }, + { + cName: "req-some-lim-and-overhead", + pod: getPod("foo", podResources{cpuRequest: "1", cpuLimit: "2", memoryRequest: "10", cpuOverhead: "5", memoryOverhead: "5"}), + expectedRequests: v1.ResourceList{ + v1.ResourceName(v1.ResourceCPU): resource.MustParse("6"), + v1.ResourceName(v1.ResourceMemory): resource.MustParse("15"), + }, + expectedLimits: v1.ResourceList{ + v1.ResourceName(v1.ResourceCPU): resource.MustParse("7"), + }, + }, + } + for idx, tc := range cases { + resRequests := PodRequests(tc.pod, PodResourcesOptions{}) + resLimits := PodLimits(tc.pod, PodResourcesOptions{}) + + if !equality.Semantic.DeepEqual(tc.expectedRequests, resRequests) { + t.Errorf("test case failure[%d]: %v, requests:\n expected:\t%v\ngot\t\t%v", idx, tc.cName, tc.expectedRequests, resRequests) + } + + if !equality.Semantic.DeepEqual(tc.expectedLimits, resLimits) { + t.Errorf("test case failure[%d]: %v, limits:\n expected:\t%v\ngot\t\t%v", idx, tc.cName, tc.expectedLimits, resLimits) + } + } +} + +func TestPodRequestsAndLimitsWithoutOverhead(t *testing.T) { + cases := []struct { + pod *v1.Pod + name string + expectedRequests v1.ResourceList + expectedLimits v1.ResourceList + }{ + { + name: "two container no overhead - should just be sum of containers", + pod: &v1.Pod{ + Spec: v1.PodSpec{ + Containers: []v1.Container{ + { + Name: "foobar", + Resources: v1.ResourceRequirements{ + Requests: v1.ResourceList{ + v1.ResourceName(v1.ResourceCPU): resource.MustParse("1"), + v1.ResourceName(v1.ResourceMemory): resource.MustParse("5"), + }, + Limits: v1.ResourceList{ + v1.ResourceName(v1.ResourceCPU): resource.MustParse("2"), + v1.ResourceName(v1.ResourceMemory): resource.MustParse("10"), + }, + }, + }, + { + Name: "foobar2", + Resources: v1.ResourceRequirements{ + Requests: v1.ResourceList{ + v1.ResourceName(v1.ResourceCPU): resource.MustParse("4"), + v1.ResourceName(v1.ResourceMemory): resource.MustParse("12"), + }, + Limits: v1.ResourceList{ + v1.ResourceName(v1.ResourceCPU): resource.MustParse("8"), + v1.ResourceName(v1.ResourceMemory): resource.MustParse("24"), + }, + }, + }, + }, + }, + }, + expectedRequests: v1.ResourceList{ + v1.ResourceName(v1.ResourceCPU): resource.MustParse("5"), + v1.ResourceName(v1.ResourceMemory): resource.MustParse("17"), + }, + expectedLimits: v1.ResourceList{ + v1.ResourceName(v1.ResourceCPU): resource.MustParse("10"), + v1.ResourceName(v1.ResourceMemory): resource.MustParse("34"), + }, + }, + { + name: "two container with overhead - shouldn't consider overhead", + pod: &v1.Pod{ + Spec: v1.PodSpec{ + Overhead: v1.ResourceList{ + v1.ResourceName(v1.ResourceCPU): resource.MustParse("3"), + v1.ResourceName(v1.ResourceMemory): resource.MustParse("8"), + }, + Containers: []v1.Container{ + { + Name: "foobar", + Resources: v1.ResourceRequirements{ + Requests: v1.ResourceList{ + v1.ResourceName(v1.ResourceCPU): resource.MustParse("1"), + v1.ResourceName(v1.ResourceMemory): resource.MustParse("5"), + }, + Limits: v1.ResourceList{ + v1.ResourceName(v1.ResourceCPU): resource.MustParse("2"), + v1.ResourceName(v1.ResourceMemory): resource.MustParse("10"), + }, + }, + }, + { + Name: "foobar2", + Resources: v1.ResourceRequirements{ + Requests: v1.ResourceList{ + v1.ResourceName(v1.ResourceCPU): resource.MustParse("4"), + v1.ResourceName(v1.ResourceMemory): resource.MustParse("12"), + }, + Limits: v1.ResourceList{ + v1.ResourceName(v1.ResourceCPU): resource.MustParse("8"), + v1.ResourceName(v1.ResourceMemory): resource.MustParse("24"), + }, + }, + }, + }, + }, + }, + expectedRequests: v1.ResourceList{ + v1.ResourceName(v1.ResourceCPU): resource.MustParse("5"), + v1.ResourceName(v1.ResourceMemory): resource.MustParse("17"), + }, + expectedLimits: v1.ResourceList{ + v1.ResourceName(v1.ResourceCPU): resource.MustParse("10"), + v1.ResourceName(v1.ResourceMemory): resource.MustParse("34"), + }, + }, + { + name: "two container with overhead, massive init - should just be the largest init", + pod: &v1.Pod{ + Spec: v1.PodSpec{ + Overhead: v1.ResourceList{ + v1.ResourceName(v1.ResourceCPU): resource.MustParse("3"), + v1.ResourceName(v1.ResourceMemory): resource.MustParse("8"), + }, + Containers: []v1.Container{ + { + Name: "foobar", + Resources: v1.ResourceRequirements{ + Requests: v1.ResourceList{ + v1.ResourceName(v1.ResourceCPU): resource.MustParse("1"), + v1.ResourceName(v1.ResourceMemory): resource.MustParse("5"), + }, + Limits: v1.ResourceList{ + v1.ResourceName(v1.ResourceCPU): resource.MustParse("2"), + v1.ResourceName(v1.ResourceMemory): resource.MustParse("10"), + }, + }, + }, + { + Name: "foobar2", + Resources: v1.ResourceRequirements{ + Requests: v1.ResourceList{ + v1.ResourceName(v1.ResourceCPU): resource.MustParse("4"), + v1.ResourceName(v1.ResourceMemory): resource.MustParse("12"), + }, + Limits: v1.ResourceList{ + v1.ResourceName(v1.ResourceCPU): resource.MustParse("8"), + v1.ResourceName(v1.ResourceMemory): resource.MustParse("24"), + }, + }, + }, + }, + InitContainers: []v1.Container{ + { + Name: "small-init", + Resources: v1.ResourceRequirements{ + Requests: v1.ResourceList{ + v1.ResourceName(v1.ResourceCPU): resource.MustParse("1"), + v1.ResourceName(v1.ResourceMemory): resource.MustParse("5"), + }, + Limits: v1.ResourceList{ + v1.ResourceName(v1.ResourceCPU): resource.MustParse("1"), + v1.ResourceName(v1.ResourceMemory): resource.MustParse("5"), + }, + }, + }, + { + Name: "big-init", + Resources: v1.ResourceRequirements{ + Requests: v1.ResourceList{ + v1.ResourceName(v1.ResourceCPU): resource.MustParse("40"), + v1.ResourceName(v1.ResourceMemory): resource.MustParse("120"), + }, + Limits: v1.ResourceList{ + v1.ResourceName(v1.ResourceCPU): resource.MustParse("80"), + v1.ResourceName(v1.ResourceMemory): resource.MustParse("240"), + }, + }, + }, + }, + }, + }, + expectedRequests: v1.ResourceList{ + v1.ResourceName(v1.ResourceCPU): resource.MustParse("40"), + v1.ResourceName(v1.ResourceMemory): resource.MustParse("120"), + }, + expectedLimits: v1.ResourceList{ + v1.ResourceName(v1.ResourceCPU): resource.MustParse("80"), + v1.ResourceName(v1.ResourceMemory): resource.MustParse("240"), + }, + }, + } + for idx, tc := range cases { + resRequests := PodRequests(tc.pod, PodResourcesOptions{ExcludeOverhead: true}) + resLimits := PodLimits(tc.pod, PodResourcesOptions{ExcludeOverhead: true}) + + if !equality.Semantic.DeepEqual(tc.expectedRequests, resRequests) { + t.Errorf("test case failure[%d]: %v, requests:\n expected:\t%v\ngot\t\t%v", idx, tc.name, tc.expectedRequests, resRequests) + } + + if !equality.Semantic.DeepEqual(tc.expectedLimits, resLimits) { + t.Errorf("test case failure[%d]: %v, limits:\n expected:\t%v\ngot\t\t%v", idx, tc.name, tc.expectedLimits, resLimits) + } + } +} + +func TestPodResourceRequests(t *testing.T) { + restartAlways := v1.ContainerRestartPolicyAlways + testCases := []struct { + description string + options PodResourcesOptions + overhead v1.ResourceList + podResizeStatus v1.PodResizeStatus + initContainers []v1.Container + containers []v1.Container + containerStatus []v1.ContainerStatus + expectedRequests v1.ResourceList + }{ + { + description: "nil options, larger init container", + expectedRequests: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("4"), + }, + initContainers: []v1.Container{ + { + Resources: v1.ResourceRequirements{ + Requests: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("4"), + }, + }, + }, + }, + containers: []v1.Container{ + { + Resources: v1.ResourceRequirements{ + Requests: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("1"), + }, + }, + }, + }, + }, + { + description: "nil options, larger containers", + expectedRequests: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("5"), + }, + initContainers: []v1.Container{ + { + Resources: v1.ResourceRequirements{ + Requests: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("2"), + }, + }, + }, + }, + containers: []v1.Container{ + { + Resources: v1.ResourceRequirements{ + Requests: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("2"), + }, + }, + }, + { + Resources: v1.ResourceRequirements{ + Requests: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("3"), + }, + }, + }, + }, + }, + { + description: "pod overhead excluded", + expectedRequests: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("5"), + }, + options: PodResourcesOptions{ + ExcludeOverhead: true, + }, + overhead: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("1"), + }, + initContainers: []v1.Container{ + { + Resources: v1.ResourceRequirements{ + Requests: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("2"), + }, + }, + }, + }, + containers: []v1.Container{ + { + Resources: v1.ResourceRequirements{ + Requests: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("2"), + }, + }, + }, + { + Resources: v1.ResourceRequirements{ + Requests: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("3"), + }, + }, + }, + }, + }, + { + description: "pod overhead included", + expectedRequests: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("6"), + v1.ResourceMemory: resource.MustParse("1Gi"), + }, + overhead: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("1"), + v1.ResourceMemory: resource.MustParse("1Gi"), + }, + initContainers: []v1.Container{ + { + Resources: v1.ResourceRequirements{ + Requests: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("2"), + }, + Limits: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("2"), + }, + }, + }, + }, + containers: []v1.Container{ + { + Resources: v1.ResourceRequirements{ + Requests: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("2"), + }, + }, + }, + { + Resources: v1.ResourceRequirements{ + Requests: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("3"), + }, + }, + }, + }, + }, + { + description: "resized, infeasible", + expectedRequests: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("2"), + }, + podResizeStatus: v1.PodResizeStatusInfeasible, + options: PodResourcesOptions{InPlacePodVerticalScalingEnabled: true}, + containers: []v1.Container{ + { + Name: "container-1", + Resources: v1.ResourceRequirements{ + Requests: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("4"), + }, + }, + }, + }, + containerStatus: []v1.ContainerStatus{ + { + Name: "container-1", + AllocatedResources: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("2"), + }, + }, + }, + }, + { + description: "resized, no resize status", + expectedRequests: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("4"), + }, + options: PodResourcesOptions{InPlacePodVerticalScalingEnabled: true}, + containers: []v1.Container{ + { + Name: "container-1", + Resources: v1.ResourceRequirements{ + Requests: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("4"), + }, + }, + }, + }, + containerStatus: []v1.ContainerStatus{ + { + Name: "container-1", + AllocatedResources: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("2"), + }, + }, + }, + }, + { + description: "resized, infeasible, feature gate disabled", + expectedRequests: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("4"), + }, + podResizeStatus: v1.PodResizeStatusInfeasible, + options: PodResourcesOptions{InPlacePodVerticalScalingEnabled: false}, + containers: []v1.Container{ + { + Name: "container-1", + Resources: v1.ResourceRequirements{ + Requests: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("4"), + }, + }, + }, + }, + containerStatus: []v1.ContainerStatus{ + { + Name: "container-1", + AllocatedResources: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("2"), + }, + }, + }, + }, + { + description: "restartable init container", + expectedRequests: v1.ResourceList{ + // restartable init + regular container + v1.ResourceCPU: resource.MustParse("2"), + }, + initContainers: []v1.Container{ + { + Name: "restartable-init-1", + RestartPolicy: &restartAlways, + Resources: v1.ResourceRequirements{ + Requests: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("1"), + }, + }, + }, + }, + containers: []v1.Container{ + { + Name: "container-1", + Resources: v1.ResourceRequirements{ + Requests: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("1"), + }, + }, + }, + }, + }, + { + description: "multiple restartable init containers", + expectedRequests: v1.ResourceList{ + // max(5, restartable init containers(3+2+1) + regular(1)) = 7 + v1.ResourceCPU: resource.MustParse("7"), + }, + initContainers: []v1.Container{ + { + Name: "init-1", + Resources: v1.ResourceRequirements{ + Requests: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("5"), + }, + }, + }, + { + Name: "restartable-init-1", + RestartPolicy: &restartAlways, + Resources: v1.ResourceRequirements{ + Requests: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("1"), + }, + }, + }, + { + Name: "restartable-init-2", + RestartPolicy: &restartAlways, + Resources: v1.ResourceRequirements{ + Requests: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("2"), + }, + }, + }, + { + Name: "restartable-init-3", + RestartPolicy: &restartAlways, + Resources: v1.ResourceRequirements{ + Requests: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("3"), + }, + }, + }, + }, + containers: []v1.Container{ + { + Name: "container-1", + Resources: v1.ResourceRequirements{ + Requests: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("1"), + }, + }, + }, + }, + }, + { + description: "multiple restartable and regular init containers", + expectedRequests: v1.ResourceList{ + // init-2 requires 5 + the previously running restartable init + // containers(1+2) = 8, the restartable init container that starts + // after it doesn't count + v1.ResourceCPU: resource.MustParse("8"), + }, + initContainers: []v1.Container{ + { + Name: "init-1", + Resources: v1.ResourceRequirements{ + Requests: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("5"), + }, + }, + }, + { + Name: "restartable-init-1", + RestartPolicy: &restartAlways, + Resources: v1.ResourceRequirements{ + Requests: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("1"), + }, + }, + }, + { + Name: "restartable-init-2", + RestartPolicy: &restartAlways, + Resources: v1.ResourceRequirements{ + Requests: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("2"), + }, + }, + }, + { + Name: "init-2", + Resources: v1.ResourceRequirements{ + Requests: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("5"), + }, + }, + }, + { + Name: "restartable-init-3", + RestartPolicy: &restartAlways, + Resources: v1.ResourceRequirements{ + Requests: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("3"), + }, + }, + }, + }, + containers: []v1.Container{ + { + Name: "container-1", + Resources: v1.ResourceRequirements{ + Requests: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("1"), + }, + }, + }, + }, + }, + { + description: "restartable-init, init and regular", + expectedRequests: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("210"), + }, + initContainers: []v1.Container{ + { + Name: "restartable-init-1", + RestartPolicy: &restartAlways, + Resources: v1.ResourceRequirements{ + Requests: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("10"), + }, + }, + }, + { + Name: "init-1", + Resources: v1.ResourceRequirements{ + Requests: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("200"), + }, + }, + }, + }, + containers: []v1.Container{ + { + Name: "container-1", + Resources: v1.ResourceRequirements{ + Requests: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("100"), + }, + }, + }, + }, + }, + } + for _, tc := range testCases { + t.Run(tc.description, func(t *testing.T) { + p := &v1.Pod{ + Spec: v1.PodSpec{ + Containers: tc.containers, + InitContainers: tc.initContainers, + Overhead: tc.overhead, + }, + Status: v1.PodStatus{ + ContainerStatuses: tc.containerStatus, + Resize: tc.podResizeStatus, + }, + } + request := PodRequests(p, tc.options) + if diff := cmp.Diff(request, tc.expectedRequests); diff != "" { + t.Errorf("got=%v, want=%v, diff=%s", request, tc.expectedRequests, diff) + } + }) + } +} + +func TestPodResourceRequestsReuse(t *testing.T) { + expectedRequests := v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("1"), + } + p := &v1.Pod{ + Spec: v1.PodSpec{ + Containers: []v1.Container{ + { + Resources: v1.ResourceRequirements{ + Requests: expectedRequests, + }, + }, + }, + }, + } + + opts := PodResourcesOptions{ + Reuse: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("25"), + }, + } + requests := PodRequests(p, opts) + + if diff := cmp.Diff(requests, expectedRequests); diff != "" { + t.Errorf("got=%v, want=%v, diff=%s", requests, expectedRequests, diff) + } + + // should re-use the maps we passed in + if diff := cmp.Diff(opts.Reuse, expectedRequests); diff != "" { + t.Errorf("got=%v, want=%v, diff=%s", requests, expectedRequests, diff) + } +} + +func TestPodResourceLimits(t *testing.T) { + restartAlways := v1.ContainerRestartPolicyAlways + testCases := []struct { + description string + options PodResourcesOptions + overhead v1.ResourceList + initContainers []v1.Container + containers []v1.Container + expectedLimits v1.ResourceList + }{ + { + description: "nil options, larger init container", + expectedLimits: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("4"), + }, + initContainers: []v1.Container{ + { + Resources: v1.ResourceRequirements{ + Limits: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("4"), + }, + }, + }, + }, + containers: []v1.Container{ + { + Resources: v1.ResourceRequirements{ + Limits: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("1"), + }, + }, + }, + }, + }, + { + description: "nil options, larger containers", + expectedLimits: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("5"), + }, + initContainers: []v1.Container{ + { + Resources: v1.ResourceRequirements{ + Limits: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("2"), + }, + }, + }, + }, + containers: []v1.Container{ + { + Resources: v1.ResourceRequirements{ + Limits: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("2"), + }, + }, + }, + { + Resources: v1.ResourceRequirements{ + Limits: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("3"), + }, + }, + }, + }, + }, + { + description: "pod overhead excluded", + expectedLimits: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("5"), + }, + options: PodResourcesOptions{ + ExcludeOverhead: true, + }, + overhead: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("1"), + }, + initContainers: []v1.Container{ + { + Resources: v1.ResourceRequirements{ + Limits: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("2"), + }, + }, + }, + }, + containers: []v1.Container{ + { + Resources: v1.ResourceRequirements{ + Limits: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("2"), + }, + }, + }, + { + Resources: v1.ResourceRequirements{ + Limits: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("3"), + }, + }, + }, + }, + }, + { + description: "pod overhead included", + overhead: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("1"), + v1.ResourceMemory: resource.MustParse("1Gi"), + }, + expectedLimits: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("6"), + // overhead is only added to non-zero limits, so there will be no expected memory limit + }, + initContainers: []v1.Container{ + { + Resources: v1.ResourceRequirements{ + Limits: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("2"), + }, + }, + }, + }, + containers: []v1.Container{ + { + Resources: v1.ResourceRequirements{ + Limits: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("2"), + }, + }, + }, + { + Resources: v1.ResourceRequirements{ + Limits: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("3"), + }, + }, + }, + }, + }, + { + description: "no limited containers should result in no limits for the pod", + expectedLimits: v1.ResourceList{}, + initContainers: []v1.Container{}, + containers: []v1.Container{ + { + // Unlimited container + }, + }, + }, + { + description: "one limited and one unlimited container should result in the limited container's limits for the pod", + expectedLimits: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("2"), + v1.ResourceMemory: resource.MustParse("2Gi"), + }, + initContainers: []v1.Container{}, + containers: []v1.Container{ + { + Resources: v1.ResourceRequirements{ + Limits: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("2"), + v1.ResourceMemory: resource.MustParse("2Gi"), + }, + }, + }, + { + // Unlimited container + }, + }, + }, + { + description: "one limited and one unlimited init container should result in the limited init container's limits for the pod", + expectedLimits: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("2"), + v1.ResourceMemory: resource.MustParse("2Gi"), + }, + initContainers: []v1.Container{ + { + Resources: v1.ResourceRequirements{ + Limits: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("2"), + v1.ResourceMemory: resource.MustParse("2Gi"), + }, + }, + }, + { + // Unlimited init container + }, + }, + containers: []v1.Container{ + { + Resources: v1.ResourceRequirements{ + Limits: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("1"), + v1.ResourceMemory: resource.MustParse("1Gi"), + }, + }, + }, + }, + }, + { + description: "restartable init container", + expectedLimits: v1.ResourceList{ + // restartable init + regular container + v1.ResourceCPU: resource.MustParse("2"), + }, + initContainers: []v1.Container{ + { + Name: "restartable-init-1", + RestartPolicy: &restartAlways, + Resources: v1.ResourceRequirements{ + Limits: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("1"), + }, + }, + }, + }, + containers: []v1.Container{ + { + Name: "container-1", + Resources: v1.ResourceRequirements{ + Limits: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("1"), + }, + }, + }, + }, + }, + { + description: "multiple restartable init containers", + expectedLimits: v1.ResourceList{ + // max(5, restartable init containers(3+2+1) + regular(1)) = 7 + v1.ResourceCPU: resource.MustParse("7"), + }, + initContainers: []v1.Container{ + { + Name: "init-1", + Resources: v1.ResourceRequirements{ + Limits: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("5"), + }, + }, + }, + { + Name: "restartable-init-1", + RestartPolicy: &restartAlways, + Resources: v1.ResourceRequirements{ + Limits: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("1"), + }, + }, + }, + { + Name: "restartable-init-2", + RestartPolicy: &restartAlways, + Resources: v1.ResourceRequirements{ + Limits: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("2"), + }, + }, + }, + { + Name: "restartable-init-3", + RestartPolicy: &restartAlways, + Resources: v1.ResourceRequirements{ + Limits: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("3"), + }, + }, + }, + }, + containers: []v1.Container{ + { + Name: "container-1", + Resources: v1.ResourceRequirements{ + Limits: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("1"), + }, + }, + }, + }, + }, + { + description: "multiple restartable and regular init containers", + expectedLimits: v1.ResourceList{ + // init-2 requires 5 + the previously running restartable init + // containers(1+2) = 8, the restartable init container that starts + // after it doesn't count + v1.ResourceCPU: resource.MustParse("8"), + }, + initContainers: []v1.Container{ + { + Name: "init-1", + Resources: v1.ResourceRequirements{ + Limits: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("5"), + }, + }, + }, + { + Name: "restartable-init-1", + RestartPolicy: &restartAlways, + Resources: v1.ResourceRequirements{ + Limits: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("1"), + }, + }, + }, + { + Name: "restartable-init-2", + RestartPolicy: &restartAlways, + Resources: v1.ResourceRequirements{ + Limits: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("2"), + }, + }, + }, + { + Name: "init-2", + Resources: v1.ResourceRequirements{ + Limits: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("5"), + }, + }, + }, + { + Name: "restartable-init-3", + RestartPolicy: &restartAlways, + Resources: v1.ResourceRequirements{ + Limits: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("3"), + }, + }, + }, + }, + containers: []v1.Container{ + { + Name: "container-1", + Resources: v1.ResourceRequirements{ + Limits: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("1"), + }, + }, + }, + }, + }, + { + description: "restartable-init, init and regular", + expectedLimits: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("210"), + }, + initContainers: []v1.Container{ + { + Name: "restartable-init-1", + RestartPolicy: &restartAlways, + Resources: v1.ResourceRequirements{ + Limits: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("10"), + }, + }, + }, + { + Name: "init-1", + Resources: v1.ResourceRequirements{ + Limits: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("200"), + }, + }, + }, + }, + containers: []v1.Container{ + { + Name: "container-1", + Resources: v1.ResourceRequirements{ + Limits: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("100"), + }, + }, + }, + }, + }, + } + for _, tc := range testCases { + t.Run(tc.description, func(t *testing.T) { + p := &v1.Pod{ + Spec: v1.PodSpec{ + Containers: tc.containers, + InitContainers: tc.initContainers, + Overhead: tc.overhead, + }, + } + limits := PodLimits(p, tc.options) + if diff := cmp.Diff(limits, tc.expectedLimits); diff != "" { + t.Errorf("got=%v, want=%v, diff=%s", limits, tc.expectedLimits, diff) + } + }) + } +} + +type podResources struct { + cpuRequest, cpuLimit, memoryRequest, memoryLimit, cpuOverhead, memoryOverhead string +} + +func getPod(cname string, resources podResources) *v1.Pod { + r := v1.ResourceRequirements{ + Limits: make(v1.ResourceList), + Requests: make(v1.ResourceList), + } + + overhead := make(v1.ResourceList) + + if resources.cpuLimit != "" { + r.Limits[v1.ResourceCPU] = resource.MustParse(resources.cpuLimit) + } + if resources.memoryLimit != "" { + r.Limits[v1.ResourceMemory] = resource.MustParse(resources.memoryLimit) + } + if resources.cpuRequest != "" { + r.Requests[v1.ResourceCPU] = resource.MustParse(resources.cpuRequest) + } + if resources.memoryRequest != "" { + r.Requests[v1.ResourceMemory] = resource.MustParse(resources.memoryRequest) + } + if resources.cpuOverhead != "" { + overhead[v1.ResourceCPU] = resource.MustParse(resources.cpuOverhead) + } + if resources.memoryOverhead != "" { + overhead[v1.ResourceMemory] = resource.MustParse(resources.memoryOverhead) + } + + return &v1.Pod{ + Spec: v1.PodSpec{ + Containers: []v1.Container{ + { + Name: cname, + Resources: r, + }, + }, + InitContainers: []v1.Container{ + { + Name: "init-" + cname, + Resources: r, + }, + }, + Overhead: overhead, + }, + } +}