Skip to content

Commit 7772214

Browse files
committed
Resource Quota enforcement changes for Pod Level Resources
1 parent 28dea49 commit 7772214

File tree

2 files changed

+106
-5
lines changed

2 files changed

+106
-5
lines changed

pkg/quota/v1/evaluator/core/pods.go

+14
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,18 @@ func (p *podEvaluator) Constraints(required []corev1.ResourceName, item runtime.
122122
return err
123123
}
124124

125+
// As mentioned in the subsequent comment, the older versions required explicit
126+
// resource requests for CPU & memory for each container if resource quotas were
127+
// enabled for these resources. This was a design flaw as resource validation is
128+
// coupled with quota enforcement. With pod-level resources
129+
// feature, container-level resources are not mandatory. Hence the check for
130+
// missing container requests, for CPU/memory resources that have quotas set,
131+
// is skipped when pod-level resources feature is enabled and resources are set
132+
// at pod level.
133+
if feature.DefaultFeatureGate.Enabled(features.PodLevelResources) && resourcehelper.IsPodLevelResourcesSet(pod) {
134+
return nil
135+
}
136+
125137
// BACKWARD COMPATIBILITY REQUIREMENT: if we quota cpu or memory, then each container
126138
// must make an explicit request for the resource. this was a mistake. it coupled
127139
// validation with resource counting, but we did this before QoS was even defined.
@@ -367,6 +379,8 @@ func PodUsageFunc(obj runtime.Object, clock clock.Clock) (corev1.ResourceList, e
367379

368380
opts := resourcehelper.PodResourcesOptions{
369381
UseStatusResources: feature.DefaultFeatureGate.Enabled(features.InPlacePodVerticalScaling),
382+
// SkipPodLevelResources is set to false when PodLevelResources feature is enabled.
383+
SkipPodLevelResources: !feature.DefaultFeatureGate.Enabled(features.PodLevelResources),
370384
}
371385
requests := resourcehelper.PodRequests(pod, opts)
372386
limits := resourcehelper.PodLimits(pod, opts)

pkg/quota/v1/evaluator/core/pods_test.go

+92-5
Original file line numberDiff line numberDiff line change
@@ -42,9 +42,10 @@ import (
4242

4343
func TestPodConstraintsFunc(t *testing.T) {
4444
testCases := map[string]struct {
45-
pod *api.Pod
46-
required []corev1.ResourceName
47-
err string
45+
pod *api.Pod
46+
required []corev1.ResourceName
47+
err string
48+
podLevelResourcesEnabled bool
4849
}{
4950
"init container resource missing": {
5051
pod: &api.Pod{
@@ -133,9 +134,30 @@ func TestPodConstraintsFunc(t *testing.T) {
133134
required: []corev1.ResourceName{corev1.ResourceMemory, corev1.ResourceCPU},
134135
err: `must specify cpu for: bar,foo; memory for: bar,foo`,
135136
},
137+
"pod-level resource set, container-level required resources missing": {
138+
pod: &api.Pod{
139+
Spec: api.PodSpec{
140+
Resources: &api.ResourceRequirements{
141+
Requests: api.ResourceList{api.ResourceCPU: resource.MustParse("1m")},
142+
},
143+
Containers: []api.Container{{
144+
Name: "foo",
145+
Resources: api.ResourceRequirements{},
146+
}, {
147+
Name: "bar",
148+
Resources: api.ResourceRequirements{},
149+
}},
150+
},
151+
},
152+
required: []corev1.ResourceName{corev1.ResourceMemory, corev1.ResourceCPU},
153+
podLevelResourcesEnabled: true,
154+
err: ``,
155+
},
136156
}
137157
evaluator := NewPodEvaluator(nil, clock.RealClock{})
138158
for testName, test := range testCases {
159+
featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.PodLevelResources, test.podLevelResourcesEnabled)
160+
139161
err := evaluator.Constraints(test.required, test.pod)
140162
switch {
141163
case err != nil && len(test.err) == 0,
@@ -158,8 +180,9 @@ func TestPodEvaluatorUsage(t *testing.T) {
158180
deletionTimestampNotPastGracePeriod := metav1.NewTime(fakeClock.Now())
159181

160182
testCases := map[string]struct {
161-
pod *api.Pod
162-
usage corev1.ResourceList
183+
pod *api.Pod
184+
usage corev1.ResourceList
185+
podLevelResourcesEnabled bool
163186
}{
164187
"init container CPU": {
165188
pod: &api.Pod{
@@ -529,10 +552,74 @@ func TestPodEvaluatorUsage(t *testing.T) {
529552
generic.ObjectCountQuotaResourceNameFor(schema.GroupResource{Resource: "pods"}): resource.MustParse("1"),
530553
},
531554
},
555+
"pod-level CPU": {
556+
pod: &api.Pod{
557+
Spec: api.PodSpec{
558+
Resources: &api.ResourceRequirements{
559+
Requests: api.ResourceList{api.ResourceCPU: resource.MustParse("1m")},
560+
Limits: api.ResourceList{api.ResourceCPU: resource.MustParse("2m")},
561+
},
562+
},
563+
},
564+
podLevelResourcesEnabled: true,
565+
usage: corev1.ResourceList{
566+
corev1.ResourceRequestsCPU: resource.MustParse("1m"),
567+
corev1.ResourceLimitsCPU: resource.MustParse("2m"),
568+
corev1.ResourcePods: resource.MustParse("1"),
569+
corev1.ResourceCPU: resource.MustParse("1m"),
570+
generic.ObjectCountQuotaResourceNameFor(schema.GroupResource{Resource: "pods"}): resource.MustParse("1"),
571+
},
572+
},
573+
"pod-level Memory": {
574+
pod: &api.Pod{
575+
Spec: api.PodSpec{
576+
Resources: &api.ResourceRequirements{
577+
Requests: api.ResourceList{api.ResourceMemory: resource.MustParse("1Mi")},
578+
Limits: api.ResourceList{api.ResourceMemory: resource.MustParse("2Mi")},
579+
},
580+
},
581+
},
582+
podLevelResourcesEnabled: true,
583+
usage: corev1.ResourceList{
584+
corev1.ResourceRequestsMemory: resource.MustParse("1Mi"),
585+
corev1.ResourceLimitsMemory: resource.MustParse("2Mi"),
586+
corev1.ResourcePods: resource.MustParse("1"),
587+
corev1.ResourceMemory: resource.MustParse("1Mi"),
588+
generic.ObjectCountQuotaResourceNameFor(schema.GroupResource{Resource: "pods"}): resource.MustParse("1"),
589+
},
590+
},
591+
"pod-level memory with container-level ephemeral storage": {
592+
pod: &api.Pod{
593+
Spec: api.PodSpec{
594+
Resources: &api.ResourceRequirements{
595+
Requests: api.ResourceList{api.ResourceMemory: resource.MustParse("1Mi")},
596+
Limits: api.ResourceList{api.ResourceMemory: resource.MustParse("2Mi")},
597+
},
598+
Containers: []api.Container{{
599+
Resources: api.ResourceRequirements{
600+
Requests: api.ResourceList{api.ResourceEphemeralStorage: resource.MustParse("32Mi")},
601+
Limits: api.ResourceList{api.ResourceEphemeralStorage: resource.MustParse("64Mi")},
602+
},
603+
}},
604+
},
605+
},
606+
podLevelResourcesEnabled: true,
607+
usage: corev1.ResourceList{
608+
corev1.ResourceEphemeralStorage: resource.MustParse("32Mi"),
609+
corev1.ResourceRequestsEphemeralStorage: resource.MustParse("32Mi"),
610+
corev1.ResourceLimitsEphemeralStorage: resource.MustParse("64Mi"),
611+
corev1.ResourcePods: resource.MustParse("1"),
612+
corev1.ResourceRequestsMemory: resource.MustParse("1Mi"),
613+
corev1.ResourceLimitsMemory: resource.MustParse("2Mi"),
614+
corev1.ResourceMemory: resource.MustParse("1Mi"),
615+
generic.ObjectCountQuotaResourceNameFor(schema.GroupResource{Resource: "pods"}): resource.MustParse("1"),
616+
},
617+
},
532618
}
533619
t.Parallel()
534620
for testName, testCase := range testCases {
535621
t.Run(testName, func(t *testing.T) {
622+
featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.PodLevelResources, testCase.podLevelResourcesEnabled)
536623
actual, err := evaluator.Usage(testCase.pod)
537624
if err != nil {
538625
t.Error(err)

0 commit comments

Comments
 (0)