From 9fe847482d55ab0c3b24320f9cd15dff7cd036eb Mon Sep 17 00:00:00 2001 From: AxeZhan Date: Fri, 6 Sep 2024 10:47:29 +0800 Subject: [PATCH 1/2] move functions --- pkg/api/v1/resource/helpers.go | 234 ---------------- .../k8s.io/component-helpers/resource/OWNERS | 13 + .../component-helpers/resource/helpers.go | 263 ++++++++++++++++++ 3 files changed, 276 insertions(+), 234 deletions(-) create mode 100644 staging/src/k8s.io/component-helpers/resource/OWNERS create mode 100644 staging/src/k8s.io/component-helpers/resource/helpers.go diff --git a/pkg/api/v1/resource/helpers.go b/pkg/api/v1/resource/helpers.go index fa958884df9..11f0c67f4ac 100644 --- a/pkg/api/v1/resource/helpers.go +++ b/pkg/api/v1/resource/helpers.go @@ -24,242 +24,8 @@ import ( v1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/resource" - - podutil "k8s.io/kubernetes/pkg/api/v1/pod" ) -// PodResourcesOptions controls the behavior of PodRequests and PodLimits. -type PodResourcesOptions struct { - // Reuse, if provided will be reused to accumulate resources and returned by the PodRequests or PodLimits - // functions. All existing values in Reuse will be lost. - Reuse v1.ResourceList - // InPlacePodVerticalScalingEnabled indicates that the in-place pod vertical scaling feature gate is enabled. - InPlacePodVerticalScalingEnabled bool - // ExcludeOverhead controls if pod overhead is excluded from the calculation. - ExcludeOverhead bool - // ContainerFn is called with the effective resources required for each container within the pod. - ContainerFn func(res v1.ResourceList, containerType podutil.ContainerType) - // NonMissingContainerRequests if provided will replace any missing container level requests for the specified resources - // with the given values. If the requests for those resources are explicitly set, even if zero, they will not be modified. - NonMissingContainerRequests v1.ResourceList -} - -// PodRequests computes the pod requests per the PodResourcesOptions supplied. If PodResourcesOptions is nil, then -// the requests are returned including pod overhead. The computation is part of the API and must be reviewed -// as an API change. -func PodRequests(pod *v1.Pod, opts PodResourcesOptions) v1.ResourceList { - // attempt to reuse the maps if passed, or allocate otherwise - reqs := reuseOrClearResourceList(opts.Reuse) - - var containerStatuses map[string]*v1.ContainerStatus - if opts.InPlacePodVerticalScalingEnabled { - containerStatuses = make(map[string]*v1.ContainerStatus, len(pod.Status.ContainerStatuses)) - for i := range pod.Status.ContainerStatuses { - containerStatuses[pod.Status.ContainerStatuses[i].Name] = &pod.Status.ContainerStatuses[i] - } - } - - for _, container := range pod.Spec.Containers { - containerReqs := container.Resources.Requests - if opts.InPlacePodVerticalScalingEnabled { - cs, found := containerStatuses[container.Name] - if found { - if pod.Status.Resize == v1.PodResizeStatusInfeasible { - containerReqs = cs.AllocatedResources.DeepCopy() - } else { - containerReqs = max(container.Resources.Requests, cs.AllocatedResources) - } - } - } - - if len(opts.NonMissingContainerRequests) > 0 { - containerReqs = applyNonMissing(containerReqs, opts.NonMissingContainerRequests) - } - - if opts.ContainerFn != nil { - opts.ContainerFn(containerReqs, podutil.Containers) - } - - addResourceList(reqs, containerReqs) - } - - restartableInitContainerReqs := v1.ResourceList{} - initContainerReqs := v1.ResourceList{} - // init containers define the minimum of any resource - // Note: In-place resize is not allowed for InitContainers, so no need to check for ResizeStatus value - // - // Let's say `InitContainerUse(i)` is the resource requirements when the i-th - // init container is initializing, then - // `InitContainerUse(i) = sum(Resources of restartable init containers with index < i) + Resources of i-th init container`. - // - // See https://github.com/kubernetes/enhancements/tree/master/keps/sig-node/753-sidecar-containers#exposing-pod-resource-requirements for the detail. - for _, container := range pod.Spec.InitContainers { - containerReqs := container.Resources.Requests - if len(opts.NonMissingContainerRequests) > 0 { - containerReqs = applyNonMissing(containerReqs, opts.NonMissingContainerRequests) - } - - if container.RestartPolicy != nil && *container.RestartPolicy == v1.ContainerRestartPolicyAlways { - // and add them to the resulting cumulative container requests - addResourceList(reqs, containerReqs) - - // track our cumulative restartable init container resources - addResourceList(restartableInitContainerReqs, containerReqs) - containerReqs = restartableInitContainerReqs - } else { - tmp := v1.ResourceList{} - addResourceList(tmp, containerReqs) - addResourceList(tmp, restartableInitContainerReqs) - containerReqs = tmp - } - - if opts.ContainerFn != nil { - opts.ContainerFn(containerReqs, podutil.InitContainers) - } - maxResourceList(initContainerReqs, containerReqs) - } - - maxResourceList(reqs, initContainerReqs) - - // Add overhead for running a pod to the sum of requests if requested: - if !opts.ExcludeOverhead && pod.Spec.Overhead != nil { - addResourceList(reqs, pod.Spec.Overhead) - } - - return reqs -} - -// applyNonMissing will return a copy of the given resource list with any missing values replaced by the nonMissing values -func applyNonMissing(reqs v1.ResourceList, nonMissing v1.ResourceList) v1.ResourceList { - cp := v1.ResourceList{} - for k, v := range reqs { - cp[k] = v.DeepCopy() - } - - for k, v := range nonMissing { - if _, found := reqs[k]; !found { - rk := cp[k] - rk.Add(v) - cp[k] = rk - } - } - return cp -} - -// PodLimits computes the pod limits per the PodResourcesOptions supplied. If PodResourcesOptions is nil, then -// the limits are returned including pod overhead for any non-zero limits. The computation is part of the API and must be reviewed -// as an API change. -func PodLimits(pod *v1.Pod, opts PodResourcesOptions) v1.ResourceList { - // attempt to reuse the maps if passed, or allocate otherwise - limits := reuseOrClearResourceList(opts.Reuse) - - for _, container := range pod.Spec.Containers { - if opts.ContainerFn != nil { - opts.ContainerFn(container.Resources.Limits, podutil.Containers) - } - addResourceList(limits, container.Resources.Limits) - } - - restartableInitContainerLimits := v1.ResourceList{} - initContainerLimits := v1.ResourceList{} - // init containers define the minimum of any resource - // - // Let's say `InitContainerUse(i)` is the resource requirements when the i-th - // init container is initializing, then - // `InitContainerUse(i) = sum(Resources of restartable init containers with index < i) + Resources of i-th init container`. - // - // See https://github.com/kubernetes/enhancements/tree/master/keps/sig-node/753-sidecar-containers#exposing-pod-resource-requirements for the detail. - for _, container := range pod.Spec.InitContainers { - containerLimits := container.Resources.Limits - // Is the init container marked as a restartable init container? - if container.RestartPolicy != nil && *container.RestartPolicy == v1.ContainerRestartPolicyAlways { - addResourceList(limits, containerLimits) - - // track our cumulative restartable init container resources - addResourceList(restartableInitContainerLimits, containerLimits) - containerLimits = restartableInitContainerLimits - } else { - tmp := v1.ResourceList{} - addResourceList(tmp, containerLimits) - addResourceList(tmp, restartableInitContainerLimits) - containerLimits = tmp - } - - if opts.ContainerFn != nil { - opts.ContainerFn(containerLimits, podutil.InitContainers) - } - maxResourceList(initContainerLimits, containerLimits) - } - - maxResourceList(limits, initContainerLimits) - - // Add overhead to non-zero limits if requested: - if !opts.ExcludeOverhead && pod.Spec.Overhead != nil { - for name, quantity := range pod.Spec.Overhead { - if value, ok := limits[name]; ok && !value.IsZero() { - value.Add(quantity) - limits[name] = value - } - } - } - - return limits -} - -// addResourceList adds the resources in newList to list. -func addResourceList(list, newList v1.ResourceList) { - for name, quantity := range newList { - if value, ok := list[name]; !ok { - list[name] = quantity.DeepCopy() - } else { - value.Add(quantity) - list[name] = value - } - } -} - -// maxResourceList sets list to the greater of list/newList for every resource in newList -func maxResourceList(list, newList v1.ResourceList) { - for name, quantity := range newList { - if value, ok := list[name]; !ok || quantity.Cmp(value) > 0 { - list[name] = quantity.DeepCopy() - } - } -} - -// max returns the result of max(a, b) for each named resource and is only used if we can't -// accumulate into an existing resource list -func max(a v1.ResourceList, b v1.ResourceList) v1.ResourceList { - result := v1.ResourceList{} - for key, value := range a { - if other, found := b[key]; found { - if value.Cmp(other) <= 0 { - result[key] = other.DeepCopy() - continue - } - } - result[key] = value.DeepCopy() - } - for key, value := range b { - if _, found := result[key]; !found { - result[key] = value.DeepCopy() - } - } - return result -} - -// reuseOrClearResourceList is a helper for avoiding excessive allocations of -// resource lists within the inner loop of resource calculations. -func reuseOrClearResourceList(reuse v1.ResourceList) v1.ResourceList { - if reuse == nil { - return make(v1.ResourceList, 4) - } - for k := range reuse { - delete(reuse, k) - } - return reuse -} - // GetResourceRequestQuantity finds and returns the request quantity for a specific resource. func GetResourceRequestQuantity(pod *v1.Pod, resourceName v1.ResourceName) resource.Quantity { requestQuantity := resource.Quantity{} diff --git a/staging/src/k8s.io/component-helpers/resource/OWNERS b/staging/src/k8s.io/component-helpers/resource/OWNERS new file mode 100644 index 00000000000..3ac6e2a21b2 --- /dev/null +++ b/staging/src/k8s.io/component-helpers/resource/OWNERS @@ -0,0 +1,13 @@ +# See the OWNERS docs at https://go.k8s.io/owners + +options: + no_parent_owners: true +approvers: + - api-approvers +reviewers: + - sig-node-reviewers + - sig-scheduling +labels: + - sig/node + - sig/scheduling + - kind/api-change \ No newline at end of file diff --git a/staging/src/k8s.io/component-helpers/resource/helpers.go b/staging/src/k8s.io/component-helpers/resource/helpers.go new file mode 100644 index 00000000000..1d83aa8973a --- /dev/null +++ b/staging/src/k8s.io/component-helpers/resource/helpers.go @@ -0,0 +1,263 @@ +/* +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 ( + v1 "k8s.io/api/core/v1" +) + +// ContainerType signifies container type +type ContainerType int + +const ( + // Containers is for normal containers + Containers ContainerType = 1 << iota + // InitContainers is for init containers + InitContainers +) + +// PodResourcesOptions controls the behavior of PodRequests and PodLimits. +type PodResourcesOptions struct { + // Reuse, if provided will be reused to accumulate resources and returned by the PodRequests or PodLimits + // functions. All existing values in Reuse will be lost. + Reuse v1.ResourceList + // InPlacePodVerticalScalingEnabled indicates that the in-place pod vertical scaling feature gate is enabled. + InPlacePodVerticalScalingEnabled bool + // ExcludeOverhead controls if pod overhead is excluded from the calculation. + ExcludeOverhead bool + // ContainerFn is called with the effective resources required for each container within the pod. + ContainerFn func(res v1.ResourceList, containerType ContainerType) + // NonMissingContainerRequests if provided will replace any missing container level requests for the specified resources + // with the given values. If the requests for those resources are explicitly set, even if zero, they will not be modified. + NonMissingContainerRequests v1.ResourceList +} + +// PodRequests computes the pod requests per the PodResourcesOptions supplied. If PodResourcesOptions is nil, then +// the requests are returned including pod overhead. The computation is part of the API and must be reviewed +// as an API change. +func PodRequests(pod *v1.Pod, opts PodResourcesOptions) v1.ResourceList { + // attempt to reuse the maps if passed, or allocate otherwise + reqs := reuseOrClearResourceList(opts.Reuse) + + var containerStatuses map[string]*v1.ContainerStatus + if opts.InPlacePodVerticalScalingEnabled { + containerStatuses = make(map[string]*v1.ContainerStatus, len(pod.Status.ContainerStatuses)) + for i := range pod.Status.ContainerStatuses { + containerStatuses[pod.Status.ContainerStatuses[i].Name] = &pod.Status.ContainerStatuses[i] + } + } + + for _, container := range pod.Spec.Containers { + containerReqs := container.Resources.Requests + if opts.InPlacePodVerticalScalingEnabled { + cs, found := containerStatuses[container.Name] + if found { + if pod.Status.Resize == v1.PodResizeStatusInfeasible { + containerReqs = cs.AllocatedResources.DeepCopy() + } else { + containerReqs = max(container.Resources.Requests, cs.AllocatedResources) + } + } + } + + if len(opts.NonMissingContainerRequests) > 0 { + containerReqs = applyNonMissing(containerReqs, opts.NonMissingContainerRequests) + } + + if opts.ContainerFn != nil { + opts.ContainerFn(containerReqs, Containers) + } + + addResourceList(reqs, containerReqs) + } + + restartableInitContainerReqs := v1.ResourceList{} + initContainerReqs := v1.ResourceList{} + // init containers define the minimum of any resource + // Note: In-place resize is not allowed for InitContainers, so no need to check for ResizeStatus value + // + // Let's say `InitContainerUse(i)` is the resource requirements when the i-th + // init container is initializing, then + // `InitContainerUse(i) = sum(Resources of restartable init containers with index < i) + Resources of i-th init container`. + // + // See https://github.com/kubernetes/enhancements/tree/master/keps/sig-node/753-sidecar-containers#exposing-pod-resource-requirements for the detail. + for _, container := range pod.Spec.InitContainers { + containerReqs := container.Resources.Requests + if len(opts.NonMissingContainerRequests) > 0 { + containerReqs = applyNonMissing(containerReqs, opts.NonMissingContainerRequests) + } + + if container.RestartPolicy != nil && *container.RestartPolicy == v1.ContainerRestartPolicyAlways { + // and add them to the resulting cumulative container requests + addResourceList(reqs, containerReqs) + + // track our cumulative restartable init container resources + addResourceList(restartableInitContainerReqs, containerReqs) + containerReqs = restartableInitContainerReqs + } else { + tmp := v1.ResourceList{} + addResourceList(tmp, containerReqs) + addResourceList(tmp, restartableInitContainerReqs) + containerReqs = tmp + } + + if opts.ContainerFn != nil { + opts.ContainerFn(containerReqs, InitContainers) + } + maxResourceList(initContainerReqs, containerReqs) + } + + maxResourceList(reqs, initContainerReqs) + + // Add overhead for running a pod to the sum of requests if requested: + if !opts.ExcludeOverhead && pod.Spec.Overhead != nil { + addResourceList(reqs, pod.Spec.Overhead) + } + + return reqs +} + +// applyNonMissing will return a copy of the given resource list with any missing values replaced by the nonMissing values +func applyNonMissing(reqs v1.ResourceList, nonMissing v1.ResourceList) v1.ResourceList { + cp := v1.ResourceList{} + for k, v := range reqs { + cp[k] = v.DeepCopy() + } + + for k, v := range nonMissing { + if _, found := reqs[k]; !found { + rk := cp[k] + rk.Add(v) + cp[k] = rk + } + } + return cp +} + +// PodLimits computes the pod limits per the PodResourcesOptions supplied. If PodResourcesOptions is nil, then +// the limits are returned including pod overhead for any non-zero limits. The computation is part of the API and must be reviewed +// as an API change. +func PodLimits(pod *v1.Pod, opts PodResourcesOptions) v1.ResourceList { + // attempt to reuse the maps if passed, or allocate otherwise + limits := reuseOrClearResourceList(opts.Reuse) + + for _, container := range pod.Spec.Containers { + if opts.ContainerFn != nil { + opts.ContainerFn(container.Resources.Limits, Containers) + } + addResourceList(limits, container.Resources.Limits) + } + + restartableInitContainerLimits := v1.ResourceList{} + initContainerLimits := v1.ResourceList{} + // init containers define the minimum of any resource + // + // Let's say `InitContainerUse(i)` is the resource requirements when the i-th + // init container is initializing, then + // `InitContainerUse(i) = sum(Resources of restartable init containers with index < i) + Resources of i-th init container`. + // + // See https://github.com/kubernetes/enhancements/tree/master/keps/sig-node/753-sidecar-containers#exposing-pod-resource-requirements for the detail. + for _, container := range pod.Spec.InitContainers { + containerLimits := container.Resources.Limits + // Is the init container marked as a restartable init container? + if container.RestartPolicy != nil && *container.RestartPolicy == v1.ContainerRestartPolicyAlways { + addResourceList(limits, containerLimits) + + // track our cumulative restartable init container resources + addResourceList(restartableInitContainerLimits, containerLimits) + containerLimits = restartableInitContainerLimits + } else { + tmp := v1.ResourceList{} + addResourceList(tmp, containerLimits) + addResourceList(tmp, restartableInitContainerLimits) + containerLimits = tmp + } + + if opts.ContainerFn != nil { + opts.ContainerFn(containerLimits, InitContainers) + } + maxResourceList(initContainerLimits, containerLimits) + } + + maxResourceList(limits, initContainerLimits) + + // Add overhead to non-zero limits if requested: + if !opts.ExcludeOverhead && pod.Spec.Overhead != nil { + for name, quantity := range pod.Spec.Overhead { + if value, ok := limits[name]; ok && !value.IsZero() { + value.Add(quantity) + limits[name] = value + } + } + } + + return limits +} + +// addResourceList adds the resources in newList to list. +func addResourceList(list, newList v1.ResourceList) { + for name, quantity := range newList { + if value, ok := list[name]; !ok { + list[name] = quantity.DeepCopy() + } else { + value.Add(quantity) + list[name] = value + } + } +} + +// maxResourceList sets list to the greater of list/newList for every resource in newList +func maxResourceList(list, newList v1.ResourceList) { + for name, quantity := range newList { + if value, ok := list[name]; !ok || quantity.Cmp(value) > 0 { + list[name] = quantity.DeepCopy() + } + } +} + +// max returns the result of max(a, b) for each named resource and is only used if we can't +// accumulate into an existing resource list +func max(a v1.ResourceList, b v1.ResourceList) v1.ResourceList { + result := v1.ResourceList{} + for key, value := range a { + if other, found := b[key]; found { + if value.Cmp(other) <= 0 { + result[key] = other.DeepCopy() + continue + } + } + result[key] = value.DeepCopy() + } + for key, value := range b { + if _, found := result[key]; !found { + result[key] = value.DeepCopy() + } + } + return result +} + +// reuseOrClearResourceList is a helper for avoiding excessive allocations of +// resource lists within the inner loop of resource calculations. +func reuseOrClearResourceList(reuse v1.ResourceList) v1.ResourceList { + if reuse == nil { + return make(v1.ResourceList, 4) + } + for k := range reuse { + delete(reuse, k) + } + return reuse +} From 2ffb568540adfb5f7182747fe2529f5eea3473bb Mon Sep 17 00:00:00 2001 From: AxeZhan Date: Fri, 25 Oct 2024 11:17:40 +0800 Subject: [PATCH 2/2] rename functions --- pkg/api/v1/resource/helpers_test.go | 1129 ---------------- .../cm/devicemanager/topology_hints.go | 4 +- pkg/kubelet/cm/helpers_linux.go | 5 +- pkg/kubelet/cm/qos_container_manager_linux.go | 2 +- pkg/kubelet/eviction/eviction_manager.go | 2 +- .../kuberuntime/kuberuntime_sandbox_linux.go | 2 +- .../cache/desired_state_of_world.go | 2 +- pkg/quota/v1/evaluator/core/pods.go | 2 +- pkg/scheduler/framework/events.go | 2 +- .../framework/plugins/noderesources/fit.go | 2 +- .../noderesources/resource_allocation.go | 2 +- pkg/scheduler/framework/types.go | 2 +- pkg/scheduler/metrics/resources/resources.go | 8 +- .../k8s.io/component-helpers/resource/OWNERS | 2 +- .../resource/helpers_test.go | 1188 +++++++++++++++++ 15 files changed, 1206 insertions(+), 1148 deletions(-) create mode 100644 staging/src/k8s.io/component-helpers/resource/helpers_test.go 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, + }, + } +}