Adding the logic to set default pod-level request as following:

1. If pod-level limit is set, pod-level request is unset and container-level request is set: derive pod-level request from container-level requests
2. If pod-level limit is set, pod-level request is unset and container-level request is unset: set pod-level request equal to pod-level limit
This commit is contained in:
ndixita 2024-10-26 01:19:52 +00:00
parent 502e0f55c4
commit a2ddde877c
5 changed files with 1574 additions and 22 deletions

View File

@ -25,6 +25,7 @@ import (
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/util/intstr"
utilfeature "k8s.io/apiserver/pkg/util/feature"
resourcehelper "k8s.io/component-helpers/resource"
"k8s.io/kubernetes/pkg/api/v1/service"
"k8s.io/kubernetes/pkg/features"
"k8s.io/kubernetes/pkg/util/parsers"
@ -217,6 +218,13 @@ func SetDefaults_Pod(obj *v1.Pod) {
}
}
}
// Pod Requests default values must be applied after container-level default values
// have been populated.
if utilfeature.DefaultFeatureGate.Enabled(features.PodLevelResources) {
defaultPodRequests(obj)
}
if obj.Spec.EnableServiceLinks == nil {
enableServiceLinks := v1.DefaultEnableServiceLinks
obj.Spec.EnableServiceLinks = &enableServiceLinks
@ -438,3 +446,56 @@ func SetDefaults_PodLogOptions(obj *v1.PodLogOptions) {
}
}
}
// defaultPodRequests applies default values for pod-level requests, only when
// pod-level limits are set, in following scenarios:
// 1. When at least one container (regular, init or sidecar) has requests set:
// The pod-level requests become equal to the effective requests of all containers
// in the pod.
// 2. When no containers have requests set: The pod-level requests become equal to
// pod-level limits.
// This defaulting behavior ensures consistent resource accounting at the pod-level
// while maintaining compatibility with the container-level specifications, as detailed
// in KEP-2837: https://github.com/kubernetes/enhancements/blob/master/keps/sig-node/2837-pod-level-resource-spec/README.md#proposed-validation--defaulting-rules
func defaultPodRequests(obj *v1.Pod) {
// We only populate defaults when the pod-level resources are partly specified already.
if obj.Spec.Resources == nil {
return
}
if len(obj.Spec.Resources.Limits) == 0 {
return
}
var podReqs v1.ResourceList
podReqs = obj.Spec.Resources.Requests
if podReqs == nil {
podReqs = make(v1.ResourceList)
}
aggrCtrReqs := resourcehelper.AggregateContainerRequests(obj, resourcehelper.PodResourcesOptions{})
// When containers specify requests for a resource (supported by
// PodLevelResources feature) and pod-level requests are not set, the pod-level requests
// default to the effective requests of all the containers for that resource.
for key, aggrCtrLim := range aggrCtrReqs {
if _, exists := podReqs[key]; !exists && resourcehelper.IsSupportedPodLevelResource(key) {
podReqs[key] = aggrCtrLim.DeepCopy()
}
}
// When no containers specify requests for a resource, the pod-level requests
// will default to match the pod-level limits, if pod-level
// limits exist for that resource.
for key, podLim := range obj.Spec.Resources.Limits {
if _, exists := podReqs[key]; !exists && resourcehelper.IsSupportedPodLevelResource(key) {
podReqs[key] = podLim.DeepCopy()
}
}
// Only set pod-level resource requests in the PodSpec if the requirements map
// contains entries after collecting container-level requests and pod-level limits.
if len(podReqs) > 0 {
obj.Spec.Resources.Requests = podReqs
}
}

View File

@ -380,6 +380,880 @@ func testPodDefaults(t *testing.T, featuresEnabled bool) {
}
}
func TestPodResourcesDefaults(t *testing.T) {
cases := []struct {
name string
podLevelResourcesEnabled bool
containers []v1.Container
podResources *v1.ResourceRequirements
expectedPodSpec v1.PodSpec
}{
{
name: "pod resources=unset, container resources=unset",
containers: []v1.Container{{Resources: v1.ResourceRequirements{}}, {Resources: v1.ResourceRequirements{}}},
expectedPodSpec: v1.PodSpec{
Containers: []v1.Container{{Resources: v1.ResourceRequirements{}}, {Resources: v1.ResourceRequirements{}}},
},
}, {
name: "pod resources=unset, container requests=unset limits=set",
containers: []v1.Container{
{
Resources: v1.ResourceRequirements{
Limits: v1.ResourceList{
"cpu": resource.MustParse("2m"),
"memory": resource.MustParse("1Mi"),
},
},
}, {
Resources: v1.ResourceRequirements{
Limits: v1.ResourceList{
"cpu": resource.MustParse("1m"),
"memory": resource.MustParse("5Mi"),
},
},
},
},
expectedPodSpec: v1.PodSpec{
Containers: []v1.Container{
{
Resources: v1.ResourceRequirements{
Requests: v1.ResourceList{
"cpu": resource.MustParse("2m"),
"memory": resource.MustParse("1Mi"),
},
Limits: v1.ResourceList{
"cpu": resource.MustParse("2m"),
"memory": resource.MustParse("1Mi"),
},
},
}, {
Resources: v1.ResourceRequirements{
Requests: v1.ResourceList{
"cpu": resource.MustParse("1m"),
"memory": resource.MustParse("5Mi"),
},
Limits: v1.ResourceList{
"cpu": resource.MustParse("1m"),
"memory": resource.MustParse("5Mi"),
},
},
},
},
},
}, {
name: "pod resources=unset, container requests=set limits=unset",
containers: []v1.Container{
{
Resources: v1.ResourceRequirements{
Requests: v1.ResourceList{
"cpu": resource.MustParse("2m"),
"memory": resource.MustParse("1Mi"),
},
},
}, {
Resources: v1.ResourceRequirements{
Requests: v1.ResourceList{
"cpu": resource.MustParse("1m"),
"memory": resource.MustParse("5Mi"),
},
},
},
},
expectedPodSpec: v1.PodSpec{
Containers: []v1.Container{
{
Resources: v1.ResourceRequirements{
Requests: v1.ResourceList{
"cpu": resource.MustParse("2m"),
"memory": resource.MustParse("1Mi"),
},
},
}, {
Resources: v1.ResourceRequirements{
Requests: v1.ResourceList{
"cpu": resource.MustParse("1m"),
"memory": resource.MustParse("5Mi"),
},
},
},
},
},
}, {
name: "pod resources=unset, container requests=set limits=set",
containers: []v1.Container{
{
Resources: v1.ResourceRequirements{
Requests: v1.ResourceList{
"cpu": resource.MustParse("1m"),
"memory": resource.MustParse("1Mi"),
},
Limits: v1.ResourceList{
"cpu": resource.MustParse("2m"),
"memory": resource.MustParse("1Mi"),
},
},
}, {
Resources: v1.ResourceRequirements{
Requests: v1.ResourceList{
"cpu": resource.MustParse("1m"),
"memory": resource.MustParse("1Mi"),
},
Limits: v1.ResourceList{
"cpu": resource.MustParse("1m"),
"memory": resource.MustParse("5Mi"),
},
},
},
},
expectedPodSpec: v1.PodSpec{
Containers: []v1.Container{
{
Resources: v1.ResourceRequirements{
Requests: v1.ResourceList{
"cpu": resource.MustParse("1m"),
"memory": resource.MustParse("1Mi"),
},
Limits: v1.ResourceList{
"cpu": resource.MustParse("2m"),
"memory": resource.MustParse("1Mi"),
},
},
}, {
Resources: v1.ResourceRequirements{
Requests: v1.ResourceList{
"cpu": resource.MustParse("1m"),
"memory": resource.MustParse("1Mi"),
},
Limits: v1.ResourceList{
"cpu": resource.MustParse("1m"),
"memory": resource.MustParse("5Mi"),
},
},
},
},
},
}, {
name: "pod requests=unset limits=set, container resources=unset",
podLevelResourcesEnabled: true,
podResources: &v1.ResourceRequirements{
Limits: v1.ResourceList{
"cpu": resource.MustParse("2m"),
"memory": resource.MustParse("1Mi"),
},
},
expectedPodSpec: v1.PodSpec{
Resources: &v1.ResourceRequirements{
Requests: v1.ResourceList{
"cpu": resource.MustParse("2m"),
"memory": resource.MustParse("1Mi"),
},
Limits: v1.ResourceList{
"cpu": resource.MustParse("2m"),
"memory": resource.MustParse("1Mi"),
},
},
},
}, {
name: "pod limits=nil, container requests=unset limits=set",
podLevelResourcesEnabled: true,
podResources: &v1.ResourceRequirements{
Limits: nil,
},
containers: []v1.Container{
{
Resources: v1.ResourceRequirements{
Limits: v1.ResourceList{
"cpu": resource.MustParse("2m"),
"memory": resource.MustParse("1Mi"),
},
},
}, {
Resources: v1.ResourceRequirements{
Limits: v1.ResourceList{
"cpu": resource.MustParse("1m"),
"memory": resource.MustParse("5Mi"),
},
},
},
},
expectedPodSpec: v1.PodSpec{
Containers: []v1.Container{
{
Resources: v1.ResourceRequirements{
Requests: v1.ResourceList{
"cpu": resource.MustParse("2m"),
"memory": resource.MustParse("1Mi"),
},
Limits: v1.ResourceList{
"cpu": resource.MustParse("2m"),
"memory": resource.MustParse("1Mi"),
},
},
}, {
Resources: v1.ResourceRequirements{
Requests: v1.ResourceList{
"cpu": resource.MustParse("1m"),
"memory": resource.MustParse("5Mi"),
},
Limits: v1.ResourceList{
"cpu": resource.MustParse("1m"),
"memory": resource.MustParse("5Mi"),
},
},
},
},
},
}, {
name: "pod limits=empty map, container requests=unset limits=set",
podLevelResourcesEnabled: true,
podResources: &v1.ResourceRequirements{
Limits: v1.ResourceList{},
},
containers: []v1.Container{
{
Resources: v1.ResourceRequirements{
Limits: v1.ResourceList{
"cpu": resource.MustParse("2m"),
"memory": resource.MustParse("1Mi"),
},
},
}, {
Resources: v1.ResourceRequirements{
Limits: v1.ResourceList{
"cpu": resource.MustParse("1m"),
"memory": resource.MustParse("5Mi"),
},
},
},
},
expectedPodSpec: v1.PodSpec{
Containers: []v1.Container{
{
Resources: v1.ResourceRequirements{
Requests: v1.ResourceList{
"cpu": resource.MustParse("2m"),
"memory": resource.MustParse("1Mi"),
},
Limits: v1.ResourceList{
"cpu": resource.MustParse("2m"),
"memory": resource.MustParse("1Mi"),
},
},
}, {
Resources: v1.ResourceRequirements{
Requests: v1.ResourceList{
"cpu": resource.MustParse("1m"),
"memory": resource.MustParse("5Mi"),
},
Limits: v1.ResourceList{
"cpu": resource.MustParse("1m"),
"memory": resource.MustParse("5Mi"),
},
},
},
},
},
}, {
name: "pod requests=empty map limits=set, container requests=unset limits=set",
podLevelResourcesEnabled: true,
containers: []v1.Container{
{
Resources: v1.ResourceRequirements{
Limits: v1.ResourceList{
"cpu": resource.MustParse("2m"),
"memory": resource.MustParse("1Mi"),
},
},
}, {
Resources: v1.ResourceRequirements{
Limits: v1.ResourceList{
"cpu": resource.MustParse("1m"),
"memory": resource.MustParse("5Mi"),
},
},
},
},
podResources: &v1.ResourceRequirements{
Requests: v1.ResourceList{},
Limits: v1.ResourceList{
"cpu": resource.MustParse("5m"),
"memory": resource.MustParse("7Mi"),
},
},
expectedPodSpec: v1.PodSpec{
Resources: &v1.ResourceRequirements{
Requests: v1.ResourceList{
"cpu": resource.MustParse("3m"),
"memory": resource.MustParse("6Mi"),
},
Limits: v1.ResourceList{
"cpu": resource.MustParse("5m"),
"memory": resource.MustParse("7Mi"),
},
},
Containers: []v1.Container{
{
Resources: v1.ResourceRequirements{
Requests: v1.ResourceList{
"cpu": resource.MustParse("2m"),
"memory": resource.MustParse("1Mi"),
},
Limits: v1.ResourceList{
"cpu": resource.MustParse("2m"),
"memory": resource.MustParse("1Mi"),
},
},
}, {
Resources: v1.ResourceRequirements{
Requests: v1.ResourceList{
"cpu": resource.MustParse("1m"),
"memory": resource.MustParse("5Mi"),
},
Limits: v1.ResourceList{
"cpu": resource.MustParse("1m"),
"memory": resource.MustParse("5Mi"),
},
},
},
},
},
}, {
name: "pod requests=nil limits=set, container requests=unset limits=set",
podLevelResourcesEnabled: true,
containers: []v1.Container{
{
Resources: v1.ResourceRequirements{
Limits: v1.ResourceList{
"cpu": resource.MustParse("2m"),
"memory": resource.MustParse("1Mi"),
},
},
}, {
Resources: v1.ResourceRequirements{
Limits: v1.ResourceList{
"cpu": resource.MustParse("1m"),
"memory": resource.MustParse("5Mi"),
},
},
},
},
podResources: &v1.ResourceRequirements{
Requests: nil,
Limits: v1.ResourceList{
"cpu": resource.MustParse("5m"),
"memory": resource.MustParse("7Mi"),
},
},
expectedPodSpec: v1.PodSpec{
Resources: &v1.ResourceRequirements{
Requests: v1.ResourceList{
"cpu": resource.MustParse("3m"),
"memory": resource.MustParse("6Mi"),
},
Limits: v1.ResourceList{
"cpu": resource.MustParse("5m"),
"memory": resource.MustParse("7Mi"),
},
},
Containers: []v1.Container{
{
Resources: v1.ResourceRequirements{
Requests: v1.ResourceList{
"cpu": resource.MustParse("2m"),
"memory": resource.MustParse("1Mi"),
},
Limits: v1.ResourceList{
"cpu": resource.MustParse("2m"),
"memory": resource.MustParse("1Mi"),
},
},
}, {
Resources: v1.ResourceRequirements{
Requests: v1.ResourceList{
"cpu": resource.MustParse("1m"),
"memory": resource.MustParse("5Mi"),
},
Limits: v1.ResourceList{
"cpu": resource.MustParse("1m"),
"memory": resource.MustParse("5Mi"),
},
},
},
},
},
}, {
name: "pod requests=unset limits=set, container requests=unset limits=set",
podLevelResourcesEnabled: true,
containers: []v1.Container{
{
Resources: v1.ResourceRequirements{
Limits: v1.ResourceList{
"cpu": resource.MustParse("2m"),
"memory": resource.MustParse("1Mi"),
},
},
}, {
Resources: v1.ResourceRequirements{
Limits: v1.ResourceList{
"cpu": resource.MustParse("1m"),
"memory": resource.MustParse("5Mi"),
},
},
},
},
podResources: &v1.ResourceRequirements{
Limits: v1.ResourceList{
"cpu": resource.MustParse("5m"),
"memory": resource.MustParse("7Mi"),
},
},
expectedPodSpec: v1.PodSpec{
Resources: &v1.ResourceRequirements{
Requests: v1.ResourceList{
"cpu": resource.MustParse("3m"),
"memory": resource.MustParse("6Mi"),
},
Limits: v1.ResourceList{
"cpu": resource.MustParse("5m"),
"memory": resource.MustParse("7Mi"),
},
},
Containers: []v1.Container{
{
Resources: v1.ResourceRequirements{
Requests: v1.ResourceList{
"cpu": resource.MustParse("2m"),
"memory": resource.MustParse("1Mi"),
},
Limits: v1.ResourceList{
"cpu": resource.MustParse("2m"),
"memory": resource.MustParse("1Mi"),
},
},
}, {
Resources: v1.ResourceRequirements{
Requests: v1.ResourceList{
"cpu": resource.MustParse("1m"),
"memory": resource.MustParse("5Mi"),
},
Limits: v1.ResourceList{
"cpu": resource.MustParse("1m"),
"memory": resource.MustParse("5Mi"),
},
},
},
},
},
}, {
name: "pod requests=unset limits=set, container requests=set limits=unset",
podLevelResourcesEnabled: true,
podResources: &v1.ResourceRequirements{
Limits: v1.ResourceList{
"cpu": resource.MustParse("4m"),
"memory": resource.MustParse("8Mi"),
},
},
containers: []v1.Container{
{
Resources: v1.ResourceRequirements{
Requests: v1.ResourceList{
"cpu": resource.MustParse("1m"),
"memory": resource.MustParse("1Mi"),
},
},
}, {
Resources: v1.ResourceRequirements{
Requests: v1.ResourceList{
"cpu": resource.MustParse("1m"),
"memory": resource.MustParse("2Mi"),
},
},
},
},
expectedPodSpec: v1.PodSpec{
Resources: &v1.ResourceRequirements{
Requests: v1.ResourceList{
"cpu": resource.MustParse("2m"),
"memory": resource.MustParse("3Mi"),
},
Limits: v1.ResourceList{
"cpu": resource.MustParse("4m"),
"memory": resource.MustParse("8Mi"),
},
},
Containers: []v1.Container{
{
Resources: v1.ResourceRequirements{
Requests: v1.ResourceList{
"cpu": resource.MustParse("1m"),
"memory": resource.MustParse("1Mi"),
},
},
}, {
Resources: v1.ResourceRequirements{
Requests: v1.ResourceList{
"cpu": resource.MustParse("1m"),
"memory": resource.MustParse("2Mi"),
},
},
},
},
},
}, {
name: "pod requests=unset cpu limits=set, container resources=unset",
podLevelResourcesEnabled: true,
podResources: &v1.ResourceRequirements{
Limits: v1.ResourceList{
"cpu": resource.MustParse("4m"),
},
},
containers: []v1.Container{
{
Resources: v1.ResourceRequirements{},
}, {
Resources: v1.ResourceRequirements{},
},
},
expectedPodSpec: v1.PodSpec{
Resources: &v1.ResourceRequirements{
Requests: v1.ResourceList{
"cpu": resource.MustParse("4m"),
},
Limits: v1.ResourceList{
"cpu": resource.MustParse("4m"),
},
},
Containers: []v1.Container{
{
Resources: v1.ResourceRequirements{},
}, {
Resources: v1.ResourceRequirements{},
},
},
},
}, {
name: "pod requests=unset limits=set, container requests=set limits=set",
podLevelResourcesEnabled: true,
podResources: &v1.ResourceRequirements{
Limits: v1.ResourceList{
"cpu": resource.MustParse("4m"),
"memory": resource.MustParse("8Mi"),
},
},
containers: []v1.Container{
{
Resources: v1.ResourceRequirements{
Requests: v1.ResourceList{
"cpu": resource.MustParse("1m"),
"memory": resource.MustParse("1Mi"),
},
Limits: v1.ResourceList{
"cpu": resource.MustParse("2m"),
"memory": resource.MustParse("1Mi"),
},
},
}, {
Resources: v1.ResourceRequirements{
Requests: v1.ResourceList{
"cpu": resource.MustParse("1m"),
"memory": resource.MustParse("2Mi"),
},
Limits: v1.ResourceList{
"cpu": resource.MustParse("1m"),
"memory": resource.MustParse("5Mi"),
},
},
},
},
expectedPodSpec: v1.PodSpec{
Resources: &v1.ResourceRequirements{
Requests: v1.ResourceList{
"cpu": resource.MustParse("2m"),
"memory": resource.MustParse("3Mi"),
},
Limits: v1.ResourceList{
"cpu": resource.MustParse("4m"),
"memory": resource.MustParse("8Mi"),
},
},
Containers: []v1.Container{
{
Resources: v1.ResourceRequirements{
Requests: v1.ResourceList{
"cpu": resource.MustParse("1m"),
"memory": resource.MustParse("1Mi"),
},
Limits: v1.ResourceList{
"cpu": resource.MustParse("2m"),
"memory": resource.MustParse("1Mi"),
},
},
}, {
Resources: v1.ResourceRequirements{
Requests: v1.ResourceList{
"cpu": resource.MustParse("1m"),
"memory": resource.MustParse("2Mi"),
},
Limits: v1.ResourceList{
"cpu": resource.MustParse("1m"),
"memory": resource.MustParse("5Mi"),
},
},
},
},
},
}, {
name: "pod requests=unset limits=set, container memory requests=set limits=unset",
podLevelResourcesEnabled: true,
podResources: &v1.ResourceRequirements{
Limits: v1.ResourceList{
"cpu": resource.MustParse("4m"),
"memory": resource.MustParse("8Mi"),
},
},
containers: []v1.Container{
{
Resources: v1.ResourceRequirements{
Requests: v1.ResourceList{
"memory": resource.MustParse("1Mi"),
},
},
}, {
Resources: v1.ResourceRequirements{
Requests: v1.ResourceList{
"memory": resource.MustParse("5Mi"),
},
},
},
},
expectedPodSpec: v1.PodSpec{
Resources: &v1.ResourceRequirements{
Requests: v1.ResourceList{
"cpu": resource.MustParse("4m"),
"memory": resource.MustParse("6Mi"),
},
Limits: v1.ResourceList{
"cpu": resource.MustParse("4m"),
"memory": resource.MustParse("8Mi"),
},
},
Containers: []v1.Container{
{
Resources: v1.ResourceRequirements{
Requests: v1.ResourceList{
"cpu": resource.MustParse("2m"),
"memory": resource.MustParse("1Mi"),
},
},
}, {
Resources: v1.ResourceRequirements{
Requests: v1.ResourceList{
"cpu": resource.MustParse("1m"),
"memory": resource.MustParse("5Mi"),
},
},
},
},
},
}, {
name: "pod limits=set, container unsupported requests=set limits=set",
podLevelResourcesEnabled: true,
podResources: &v1.ResourceRequirements{
Limits: v1.ResourceList{
"cpu": resource.MustParse("2m"),
"memory": resource.MustParse("1Mi"),
},
},
containers: []v1.Container{
{
Resources: v1.ResourceRequirements{
Limits: v1.ResourceList{
"storage": resource.MustParse("1Mi"),
},
},
}, {
Resources: v1.ResourceRequirements{
Limits: v1.ResourceList{
"ephemeral-storage": resource.MustParse("5Mi"),
},
},
},
},
expectedPodSpec: v1.PodSpec{
Resources: &v1.ResourceRequirements{
Requests: v1.ResourceList{
"cpu": resource.MustParse("2m"),
"memory": resource.MustParse("1Mi"),
},
Limits: v1.ResourceList{
"cpu": resource.MustParse("2m"),
"memory": resource.MustParse("1Mi"),
},
},
Containers: []v1.Container{
{
Resources: v1.ResourceRequirements{
Requests: v1.ResourceList{
"storage": resource.MustParse("1Mi"),
},
Limits: v1.ResourceList{
"storage": resource.MustParse("1Mi"),
},
},
}, {
Resources: v1.ResourceRequirements{
Requests: v1.ResourceList{
"ephemeral-storage": resource.MustParse("5Mi"),
},
Limits: v1.ResourceList{
"ephemeral-storage": resource.MustParse("5Mi"),
},
},
},
},
},
}, {
name: "pod unsupported resources limits=set, container unsupported requests=set limits=set",
podLevelResourcesEnabled: true,
podResources: &v1.ResourceRequirements{
Limits: v1.ResourceList{
"storage": resource.MustParse("1Mi"),
},
},
containers: []v1.Container{
{
Resources: v1.ResourceRequirements{
Limits: v1.ResourceList{
"storage": resource.MustParse("1Mi"),
},
},
}, {
Resources: v1.ResourceRequirements{
Limits: v1.ResourceList{
"ephemeral-storage": resource.MustParse("5Mi"),
},
},
},
},
expectedPodSpec: v1.PodSpec{
Resources: &v1.ResourceRequirements{
Requests: v1.ResourceList{},
Limits: v1.ResourceList{
"storage": resource.MustParse("1Mi"),
},
},
Containers: []v1.Container{
{
Resources: v1.ResourceRequirements{
Requests: v1.ResourceList{
"storage": resource.MustParse("1Mi"),
},
Limits: v1.ResourceList{
"storage": resource.MustParse("1Mi"),
},
},
}, {
Resources: v1.ResourceRequirements{
Requests: v1.ResourceList{
"ephemeral-storage": resource.MustParse("5Mi"),
},
Limits: v1.ResourceList{
"ephemeral-storage": resource.MustParse("5Mi"),
},
},
},
},
},
}, {
name: "pod supported and unsupported resources limits=set, container unsupported requests=set limits=set",
podLevelResourcesEnabled: true,
podResources: &v1.ResourceRequirements{
Limits: v1.ResourceList{
"cpu": resource.MustParse("2m"),
"memory": resource.MustParse("1Mi"),
"storage": resource.MustParse("1Mi"),
},
},
containers: []v1.Container{
{
Resources: v1.ResourceRequirements{
Limits: v1.ResourceList{
"storage": resource.MustParse("1Mi"),
},
},
}, {
Resources: v1.ResourceRequirements{
Limits: v1.ResourceList{
"ephemeral-storage": resource.MustParse("5Mi"),
},
},
},
},
expectedPodSpec: v1.PodSpec{
Resources: &v1.ResourceRequirements{
Requests: v1.ResourceList{
"cpu": resource.MustParse("2m"),
"memory": resource.MustParse("1Mi"),
},
Limits: v1.ResourceList{
"cpu": resource.MustParse("2m"),
"memory": resource.MustParse("1Mi"),
},
},
Containers: []v1.Container{
{
Resources: v1.ResourceRequirements{
Requests: v1.ResourceList{
"storage": resource.MustParse("1Mi"),
},
Limits: v1.ResourceList{
"storage": resource.MustParse("1Mi"),
},
},
}, {
Resources: v1.ResourceRequirements{
Requests: v1.ResourceList{
"ephemeral-storage": resource.MustParse("5Mi"),
},
Limits: v1.ResourceList{
"ephemeral-storage": resource.MustParse("5Mi"),
},
},
},
},
},
}}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
if tc.podLevelResourcesEnabled {
featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.PodLevelResources, true)
}
spec := v1.PodSpec{
Containers: tc.containers,
Resources: tc.podResources,
}
p := v1.Pod{Spec: *spec.DeepCopy()}
corev1.SetDefaults_Pod(&p)
for i, container := range p.Spec.Containers {
for resource, quantity := range container.Resources.Requests {
if quantity.Cmp(tc.expectedPodSpec.Containers[i].Resources.Requests[resource]) != 0 {
t.Errorf("got: %v, expected: %v", quantity, tc.expectedPodSpec.Containers[i].Resources.Requests[resource])
}
}
}
if tc.podResources != nil {
for resource, quantity := range p.Spec.Resources.Requests {
if quantity.Cmp(tc.expectedPodSpec.Resources.Requests[resource]) != 0 {
t.Errorf("got: %v, expected: %v", quantity, tc.expectedPodSpec.Resources.Requests[resource])
}
}
}
})
}
}
func TestPodHostNetworkDefaults(t *testing.T) {
cases := []struct {
name string

View File

@ -20656,6 +20656,7 @@ func TestValidateOSFields(t *testing.T) {
"ResourceClaims[*].Name",
"ResourceClaims[*].ResourceClaimName",
"ResourceClaims[*].ResourceClaimTemplateName",
"Resources",
"RestartPolicy",
"RuntimeClassName",
"SchedulerName",

View File

@ -18,6 +18,7 @@ package resource
import (
v1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/util/sets"
)
// ContainerType signifies container type
@ -46,15 +47,101 @@ type PodResourcesOptions struct {
// 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
// SkipPodLevelResources controls whether pod-level resources should be skipped
// from the calculation. If pod-level resources are not set in PodSpec,
// pod-level resources will always be skipped.
SkipPodLevelResources bool
}
// 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.
var supportedPodLevelResources = sets.New(v1.ResourceCPU, v1.ResourceMemory)
func SupportedPodLevelResources() sets.Set[v1.ResourceName] {
return supportedPodLevelResources
}
// IsSupportedPodLevelResources checks if a given resource is supported by pod-level
// resource management through the PodLevelResources feature. Returns true if
// the resource is supported.
func IsSupportedPodLevelResource(name v1.ResourceName) bool {
return supportedPodLevelResources.Has(name)
}
// IsPodLevelResourcesSet check if PodLevelResources pod-level resources are set.
// It returns true if either the Requests or Limits maps are non-empty.
func IsPodLevelResourcesSet(pod *v1.Pod) bool {
if pod.Spec.Resources == nil {
return false
}
if (len(pod.Spec.Resources.Requests) + len(pod.Spec.Resources.Limits)) == 0 {
return false
}
for resourceName := range pod.Spec.Resources.Requests {
if IsSupportedPodLevelResource(resourceName) {
return true
}
}
for resourceName := range pod.Spec.Resources.Limits {
if IsSupportedPodLevelResource(resourceName) {
return true
}
}
return false
}
// IsPodLevelRequestsSet checks if pod-level requests are set. It returns true if
// Requests map is non-empty.
func IsPodLevelRequestsSet(pod *v1.Pod) bool {
if pod.Spec.Resources == nil {
return false
}
if len(pod.Spec.Resources.Requests) == 0 {
return false
}
for resourceName := range pod.Spec.Resources.Requests {
if IsSupportedPodLevelResource(resourceName) {
return true
}
}
return false
}
// PodRequests computes the total pod requests per the PodResourcesOptions supplied.
// If PodResourcesOptions is nil, then the requests are returned including pod overhead.
// If the PodLevelResources feature is enabled AND the pod-level resources are set,
// those pod-level values are used in calculating Pod Requests.
// The computation is part of the API and must be reviewed as an API change.
func PodRequests(pod *v1.Pod, opts PodResourcesOptions) v1.ResourceList {
reqs := AggregateContainerRequests(pod, opts)
if !opts.SkipPodLevelResources && IsPodLevelRequestsSet(pod) {
for resourceName, quantity := range pod.Spec.Resources.Requests {
if IsSupportedPodLevelResource(resourceName) {
reqs[resourceName] = quantity
}
}
}
// 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
}
// AggregateContainerRequests computes the total resource requests of all the containers
// in a pod. This computation folows the formula defined in the KEP for sidecar
// containers. See https://github.com/kubernetes/enhancements/tree/master/keps/sig-node/753-sidecar-containers#resources-calculation-for-scheduling-and-pod-admission
// for more details.
func AggregateContainerRequests(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.UseStatusResources {
containerStatuses = make(map[string]*v1.ContainerStatus, len(pod.Status.ContainerStatuses))
@ -124,12 +211,6 @@ func PodRequests(pod *v1.Pod, opts PodResourcesOptions) v1.ResourceList {
}
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
}
@ -155,8 +236,35 @@ func applyNonMissing(reqs v1.ResourceList, nonMissing v1.ResourceList) v1.Resour
// 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)
limits := AggregateContainerLimits(pod, opts)
if !opts.SkipPodLevelResources && IsPodLevelResourcesSet(pod) {
for resourceName, quantity := range pod.Spec.Resources.Limits {
if IsSupportedPodLevelResource(resourceName) {
limits[resourceName] = quantity
}
}
}
// 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
}
// AggregateContainerLimits computes the aggregated resource limits of all the containers
// in a pod. This computation follows the formula defined in the KEP for sidecar
// containers. See https://github.com/kubernetes/enhancements/tree/master/keps/sig-node/753-sidecar-containers#resources-calculation-for-scheduling-and-pod-admission
// for more details.
func AggregateContainerLimits(pod *v1.Pod, opts PodResourcesOptions) v1.ResourceList {
// attempt to reuse the maps if passed, or allocate otherwise
limits := reuseOrClearResourceList(opts.Reuse)
var containerStatuses map[string]*v1.ContainerStatus
if opts.UseStatusResources {
containerStatuses = make(map[string]*v1.ContainerStatus, len(pod.Status.ContainerStatuses))
@ -216,17 +324,6 @@ func PodLimits(pod *v1.Pod, opts PodResourcesOptions) v1.ResourceList {
}
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
}

View File

@ -1228,10 +1228,529 @@ func TestPodResourceLimits(t *testing.T) {
}
}
func TestIsPodLevelResourcesSet(t *testing.T) {
testCases := []struct {
name string
podResources *v1.ResourceRequirements
expected bool
}{
{
name: "nil resources struct",
expected: false,
},
{
name: "empty resources struct",
podResources: &v1.ResourceRequirements{},
expected: false,
},
{
name: "only unsupported resource requests set",
podResources: &v1.ResourceRequirements{
Requests: v1.ResourceList{v1.ResourceEphemeralStorage: resource.MustParse("1Mi")},
},
expected: false,
},
{
name: "only unsupported resource limits set",
podResources: &v1.ResourceRequirements{
Limits: v1.ResourceList{v1.ResourceEphemeralStorage: resource.MustParse("1Mi")},
},
expected: false,
},
{
name: "unsupported and suported resources requests set",
podResources: &v1.ResourceRequirements{
Requests: v1.ResourceList{
v1.ResourceEphemeralStorage: resource.MustParse("1Mi"),
v1.ResourceCPU: resource.MustParse("1m"),
},
},
expected: true,
},
{
name: "unsupported and suported resources limits set",
podResources: &v1.ResourceRequirements{
Limits: v1.ResourceList{
v1.ResourceEphemeralStorage: resource.MustParse("1Mi"),
v1.ResourceCPU: resource.MustParse("1m"),
},
},
expected: true,
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
testPod := &v1.Pod{Spec: v1.PodSpec{Resources: tc.podResources}}
if got := IsPodLevelResourcesSet(testPod); got != tc.expected {
t.Errorf("got=%t, want=%t", got, tc.expected)
}
})
}
}
func TestPodLevelResourceRequests(t *testing.T) {
restartAlways := v1.ContainerRestartPolicyAlways
testCases := []struct {
name string
opts PodResourcesOptions
podResources v1.ResourceRequirements
overhead v1.ResourceList
initContainers []v1.Container
containers []v1.Container
expectedRequests v1.ResourceList
}{
{
name: "nil",
expectedRequests: v1.ResourceList{},
},
{
name: "pod level memory resource with SkipPodLevelResources true",
podResources: v1.ResourceRequirements{Requests: v1.ResourceList{v1.ResourceMemory: resource.MustParse("2Mi")}},
opts: PodResourcesOptions{SkipPodLevelResources: true},
expectedRequests: v1.ResourceList{},
},
{
name: "pod level memory resource with SkipPodLevelResources false",
podResources: v1.ResourceRequirements{Requests: v1.ResourceList{v1.ResourceMemory: resource.MustParse("2Mi")}},
opts: PodResourcesOptions{SkipPodLevelResources: false},
expectedRequests: v1.ResourceList{v1.ResourceMemory: resource.MustParse("2Mi")},
},
{
name: "pod level memory and container level cpu resources with SkipPodLevelResources false",
podResources: v1.ResourceRequirements{Requests: v1.ResourceList{v1.ResourceMemory: resource.MustParse("2Mi")}},
containers: []v1.Container{
{
Resources: v1.ResourceRequirements{Requests: v1.ResourceList{v1.ResourceCPU: resource.MustParse("2m")}},
},
},
opts: PodResourcesOptions{SkipPodLevelResources: false},
expectedRequests: v1.ResourceList{v1.ResourceMemory: resource.MustParse("2Mi"), v1.ResourceCPU: resource.MustParse("2m")},
},
{
name: "pod level unsupported resources set at both pod-level and container-level with SkipPodLevelResources false",
podResources: v1.ResourceRequirements{Requests: v1.ResourceList{v1.ResourceStorage: resource.MustParse("2Mi")}},
containers: []v1.Container{
{
Resources: v1.ResourceRequirements{Requests: v1.ResourceList{v1.ResourceStorage: resource.MustParse("3Mi")}},
},
},
opts: PodResourcesOptions{SkipPodLevelResources: false},
expectedRequests: v1.ResourceList{v1.ResourceStorage: resource.MustParse("3Mi")},
},
{
name: "pod level unsupported resources set at pod-level with SkipPodLevelResources false",
podResources: v1.ResourceRequirements{Requests: v1.ResourceList{v1.ResourceStorage: resource.MustParse("2Mi")}},
containers: []v1.Container{
{
Resources: v1.ResourceRequirements{Requests: v1.ResourceList{v1.ResourceMemory: resource.MustParse("3Mi")}},
},
},
opts: PodResourcesOptions{SkipPodLevelResources: false},
expectedRequests: v1.ResourceList{v1.ResourceMemory: resource.MustParse("3Mi")},
},
{
name: "only container level resources set with SkipPodLevelResources false",
containers: []v1.Container{
{
Resources: v1.ResourceRequirements{
Requests: v1.ResourceList{
v1.ResourceMemory: resource.MustParse("3Mi"),
v1.ResourceCPU: resource.MustParse("2m"),
},
},
},
},
opts: PodResourcesOptions{SkipPodLevelResources: false},
expectedRequests: v1.ResourceList{v1.ResourceMemory: resource.MustParse("3Mi"), v1.ResourceCPU: resource.MustParse("2m")},
},
{
name: "both container-level and pod-level resources set with SkipPodLevelResources false",
podResources: v1.ResourceRequirements{
Requests: v1.ResourceList{
v1.ResourceMemory: resource.MustParse("6Mi"),
v1.ResourceCPU: resource.MustParse("8m"),
},
},
containers: []v1.Container{
{
Resources: v1.ResourceRequirements{
Requests: v1.ResourceList{
v1.ResourceMemory: resource.MustParse("3Mi"),
v1.ResourceCPU: resource.MustParse("2m"),
},
},
},
},
opts: PodResourcesOptions{SkipPodLevelResources: false},
expectedRequests: v1.ResourceList{v1.ResourceMemory: resource.MustParse("6Mi"), v1.ResourceCPU: resource.MustParse("8m")},
},
{
name: "container-level resources and init container set with SkipPodLevelResources false",
containers: []v1.Container{
{
Resources: v1.ResourceRequirements{
Requests: v1.ResourceList{
v1.ResourceMemory: resource.MustParse("3Mi"),
v1.ResourceCPU: resource.MustParse("2m"),
},
},
},
},
initContainers: []v1.Container{
{
Resources: v1.ResourceRequirements{
Requests: v1.ResourceList{
v1.ResourceMemory: resource.MustParse("5Mi"),
v1.ResourceCPU: resource.MustParse("4m"),
},
},
},
},
opts: PodResourcesOptions{SkipPodLevelResources: false},
expectedRequests: v1.ResourceList{v1.ResourceMemory: resource.MustParse("5Mi"), v1.ResourceCPU: resource.MustParse("4m")},
},
{
name: "container-level resources and init container set with SkipPodLevelResources false",
containers: []v1.Container{
{
Resources: v1.ResourceRequirements{
Requests: v1.ResourceList{
v1.ResourceMemory: resource.MustParse("3Mi"),
v1.ResourceCPU: resource.MustParse("2m"),
},
},
},
},
initContainers: []v1.Container{
{
Resources: v1.ResourceRequirements{
Requests: v1.ResourceList{
v1.ResourceMemory: resource.MustParse("5Mi"),
v1.ResourceCPU: resource.MustParse("4m"),
},
},
},
},
opts: PodResourcesOptions{SkipPodLevelResources: true},
expectedRequests: v1.ResourceList{v1.ResourceMemory: resource.MustParse("5Mi"), v1.ResourceCPU: resource.MustParse("4m")},
},
{
name: "container-level resources and sidecar container set with SkipPodLevelResources false",
containers: []v1.Container{
{
Resources: v1.ResourceRequirements{
Requests: v1.ResourceList{
v1.ResourceMemory: resource.MustParse("3Mi"),
v1.ResourceCPU: resource.MustParse("2m"),
},
},
},
},
initContainers: []v1.Container{
{
Resources: v1.ResourceRequirements{
Requests: v1.ResourceList{
v1.ResourceMemory: resource.MustParse("5Mi"),
v1.ResourceCPU: resource.MustParse("4m"),
},
},
RestartPolicy: &restartAlways,
},
},
opts: PodResourcesOptions{SkipPodLevelResources: false},
expectedRequests: v1.ResourceList{v1.ResourceMemory: resource.MustParse("8Mi"), v1.ResourceCPU: resource.MustParse("6m")},
},
{
name: "container-level resources, init and sidecar container set with SkipPodLevelResources false",
containers: []v1.Container{
{
Resources: v1.ResourceRequirements{
Requests: v1.ResourceList{
v1.ResourceMemory: resource.MustParse("3Mi"),
v1.ResourceCPU: resource.MustParse("2m"),
},
},
},
},
initContainers: []v1.Container{
{
Resources: v1.ResourceRequirements{
Requests: v1.ResourceList{
v1.ResourceMemory: resource.MustParse("5Mi"),
v1.ResourceCPU: resource.MustParse("4m"),
},
},
RestartPolicy: &restartAlways,
},
{
Resources: v1.ResourceRequirements{
Requests: v1.ResourceList{
v1.ResourceMemory: resource.MustParse("6Mi"),
v1.ResourceCPU: resource.MustParse("8m"),
},
},
},
},
opts: PodResourcesOptions{SkipPodLevelResources: false},
expectedRequests: v1.ResourceList{v1.ResourceMemory: resource.MustParse("11Mi"), v1.ResourceCPU: resource.MustParse("12m")},
},
{
name: "pod-level resources, container-level resources, init and sidecar container set with SkipPodLevelResources false",
podResources: v1.ResourceRequirements{
Requests: v1.ResourceList{
v1.ResourceMemory: resource.MustParse("15Mi"),
v1.ResourceCPU: resource.MustParse("18m"),
},
},
containers: []v1.Container{
{
Resources: v1.ResourceRequirements{
Requests: v1.ResourceList{
v1.ResourceMemory: resource.MustParse("3Mi"),
v1.ResourceCPU: resource.MustParse("2m"),
},
},
},
},
initContainers: []v1.Container{
{
Resources: v1.ResourceRequirements{
Requests: v1.ResourceList{
v1.ResourceMemory: resource.MustParse("5Mi"),
v1.ResourceCPU: resource.MustParse("4m"),
},
},
RestartPolicy: &restartAlways,
},
{
Resources: v1.ResourceRequirements{
Requests: v1.ResourceList{
v1.ResourceMemory: resource.MustParse("6Mi"),
v1.ResourceCPU: resource.MustParse("8m"),
},
},
},
},
opts: PodResourcesOptions{SkipPodLevelResources: false},
expectedRequests: v1.ResourceList{v1.ResourceMemory: resource.MustParse("15Mi"), v1.ResourceCPU: resource.MustParse("18m")},
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
podReqs := PodRequests(getPodLevelResourcesPod(tc.podResources, tc.overhead, tc.containers, tc.initContainers), tc.opts)
if diff := cmp.Diff(podReqs, tc.expectedRequests); diff != "" {
t.Errorf("got=%v, want=%v, diff=%s", podReqs, tc.expectedRequests, diff)
}
})
}
}
func TestAggregateContainerRequestsAndLimits(t *testing.T) {
restartAlways := v1.ContainerRestartPolicyAlways
cases := []struct {
containers []v1.Container
initContainers []v1.Container
name string
expectedRequests v1.ResourceList
expectedLimits v1.ResourceList
}{
{
name: "one container with limits",
containers: []v1.Container{
{
Resources: v1.ResourceRequirements{
Limits: v1.ResourceList{v1.ResourceName(v1.ResourceCPU): resource.MustParse("9")},
},
},
},
expectedRequests: v1.ResourceList{},
expectedLimits: v1.ResourceList{
v1.ResourceName(v1.ResourceCPU): resource.MustParse("9"),
},
},
{
name: "two containers with limits",
containers: []v1.Container{
{
Resources: v1.ResourceRequirements{
Limits: v1.ResourceList{v1.ResourceName(v1.ResourceCPU): resource.MustParse("9")},
},
},
{
Resources: v1.ResourceRequirements{
Limits: v1.ResourceList{v1.ResourceName(v1.ResourceCPU): resource.MustParse("9")},
},
},
},
expectedRequests: v1.ResourceList{},
expectedLimits: v1.ResourceList{
v1.ResourceName(v1.ResourceCPU): resource.MustParse("18"),
},
},
{
name: "one container with requests",
containers: []v1.Container{
{
Resources: v1.ResourceRequirements{
Requests: v1.ResourceList{v1.ResourceName(v1.ResourceCPU): resource.MustParse("9")},
},
},
},
expectedRequests: v1.ResourceList{
v1.ResourceName(v1.ResourceCPU): resource.MustParse("9"),
},
expectedLimits: v1.ResourceList{},
},
{
name: "two containers with requests",
containers: []v1.Container{
{
Resources: v1.ResourceRequirements{
Requests: v1.ResourceList{v1.ResourceName(v1.ResourceCPU): resource.MustParse("9")},
},
},
{
Resources: v1.ResourceRequirements{
Requests: v1.ResourceList{v1.ResourceName(v1.ResourceCPU): resource.MustParse("9")},
},
},
},
expectedRequests: v1.ResourceList{
v1.ResourceName(v1.ResourceCPU): resource.MustParse("18"),
},
expectedLimits: v1.ResourceList{},
},
{
name: "regular and init containers with requests",
containers: []v1.Container{
{
Resources: v1.ResourceRequirements{
Requests: v1.ResourceList{v1.ResourceName(v1.ResourceCPU): resource.MustParse("9")},
},
},
},
initContainers: []v1.Container{
{
Resources: v1.ResourceRequirements{
Requests: v1.ResourceList{v1.ResourceName(v1.ResourceCPU): resource.MustParse("9")},
},
},
},
expectedRequests: v1.ResourceList{
v1.ResourceName(v1.ResourceCPU): resource.MustParse("9"),
},
expectedLimits: v1.ResourceList{},
},
{
name: "regular, init and sidecar containers with requests",
containers: []v1.Container{
{
Resources: v1.ResourceRequirements{
Requests: v1.ResourceList{v1.ResourceName(v1.ResourceCPU): resource.MustParse("9")},
},
},
},
initContainers: []v1.Container{
{
Resources: v1.ResourceRequirements{
Requests: v1.ResourceList{v1.ResourceName(v1.ResourceCPU): resource.MustParse("8")},
},
RestartPolicy: &restartAlways,
},
{
Resources: v1.ResourceRequirements{
Requests: v1.ResourceList{v1.ResourceName(v1.ResourceCPU): resource.MustParse("6")},
},
},
},
expectedRequests: v1.ResourceList{
v1.ResourceName(v1.ResourceCPU): resource.MustParse("17"),
},
expectedLimits: v1.ResourceList{},
},
{
name: "regular and init containers with limits",
containers: []v1.Container{
{
Resources: v1.ResourceRequirements{
Limits: v1.ResourceList{v1.ResourceName(v1.ResourceCPU): resource.MustParse("9")},
},
},
},
initContainers: []v1.Container{
{
Resources: v1.ResourceRequirements{
Limits: v1.ResourceList{v1.ResourceName(v1.ResourceCPU): resource.MustParse("9")},
},
},
},
expectedRequests: v1.ResourceList{},
expectedLimits: v1.ResourceList{
v1.ResourceName(v1.ResourceCPU): resource.MustParse("9"),
},
},
{
name: "regular, init and sidecar containers with limits",
containers: []v1.Container{
{
Resources: v1.ResourceRequirements{
Limits: v1.ResourceList{v1.ResourceName(v1.ResourceCPU): resource.MustParse("9")},
},
},
},
initContainers: []v1.Container{
{
Resources: v1.ResourceRequirements{
Limits: v1.ResourceList{v1.ResourceName(v1.ResourceCPU): resource.MustParse("8")},
},
RestartPolicy: &restartAlways,
},
{
Resources: v1.ResourceRequirements{
Limits: v1.ResourceList{v1.ResourceName(v1.ResourceCPU): resource.MustParse("6")},
},
},
},
expectedRequests: v1.ResourceList{},
expectedLimits: v1.ResourceList{
v1.ResourceName(v1.ResourceCPU): resource.MustParse("17"),
},
},
}
for idx, tc := range cases {
testPod := &v1.Pod{Spec: v1.PodSpec{Containers: tc.containers, InitContainers: tc.initContainers}}
resRequests := AggregateContainerRequests(testPod, PodResourcesOptions{})
resLimits := AggregateContainerLimits(testPod, 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.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
}
func getPodLevelResourcesPod(podResources v1.ResourceRequirements, overhead v1.ResourceList, containers, initContainers []v1.Container) *v1.Pod {
return &v1.Pod{
Spec: v1.PodSpec{
Resources: &podResources,
Containers: containers,
InitContainers: initContainers,
Overhead: overhead,
},
}
}
// TODO(ndixita): refactor to re-use getPodResourcesPod()
func getPod(cname string, resources podResources) *v1.Pod {
r := v1.ResourceRequirements{
Limits: make(v1.ResourceList),