mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-30 15:05:27 +00:00
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:
parent
502e0f55c4
commit
a2ddde877c
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -20656,6 +20656,7 @@ func TestValidateOSFields(t *testing.T) {
|
||||
"ResourceClaims[*].Name",
|
||||
"ResourceClaims[*].ResourceClaimName",
|
||||
"ResourceClaims[*].ResourceClaimTemplateName",
|
||||
"Resources",
|
||||
"RestartPolicy",
|
||||
"RuntimeClassName",
|
||||
"SchedulerName",
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
@ -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),
|
||||
|
Loading…
Reference in New Issue
Block a user