Skip to content

Commit 2691a29

Browse files
authored
Merge pull request kubernetes#128683 from AnishShah/validation
[FG:InPlacePodVerticalScaling] Disallow removing requests & limits for Burstable pods.
2 parents c25f5ee + 7680f0f commit 2691a29

File tree

3 files changed

+200
-13
lines changed

3 files changed

+200
-13
lines changed

pkg/apis/core/validation/validation.go

+36
Original file line numberDiff line numberDiff line change
@@ -5596,6 +5596,29 @@ func ValidatePodResize(newPod, oldPod *core.Pod, opts PodValidationOptions) fiel
55965596
allErrs = append(allErrs, field.Forbidden(specPath, "Pod running on node without support for resize"))
55975597
}
55985598

5599+
// Do not allow removing resource requests/limits on resize.
5600+
if utilfeature.DefaultFeatureGate.Enabled(features.SidecarContainers) {
5601+
for ix, ctr := range oldPod.Spec.InitContainers {
5602+
if ctr.RestartPolicy != nil && *ctr.RestartPolicy != core.ContainerRestartPolicyAlways {
5603+
continue
5604+
}
5605+
if resourcesRemoved(newPod.Spec.InitContainers[ix].Resources.Requests, ctr.Resources.Requests) {
5606+
allErrs = append(allErrs, field.Forbidden(specPath.Child("initContainers").Index(ix).Child("resources").Child("requests"), "resource requests cannot be removed"))
5607+
}
5608+
if resourcesRemoved(newPod.Spec.InitContainers[ix].Resources.Limits, ctr.Resources.Limits) {
5609+
allErrs = append(allErrs, field.Forbidden(specPath.Child("initContainers").Index(ix).Child("resources").Child("limits"), "resource limits cannot be removed"))
5610+
}
5611+
}
5612+
}
5613+
for ix, ctr := range oldPod.Spec.Containers {
5614+
if resourcesRemoved(newPod.Spec.Containers[ix].Resources.Requests, ctr.Resources.Requests) {
5615+
allErrs = append(allErrs, field.Forbidden(specPath.Child("containers").Index(ix).Child("resources").Child("requests"), "resource requests cannot be removed"))
5616+
}
5617+
if resourcesRemoved(newPod.Spec.Containers[ix].Resources.Limits, ctr.Resources.Limits) {
5618+
allErrs = append(allErrs, field.Forbidden(specPath.Child("containers").Index(ix).Child("resources").Child("limits"), "resource limits cannot be removed"))
5619+
}
5620+
}
5621+
55995622
// Ensure that only CPU and memory resources are mutable.
56005623
originalCPUMemPodSpec := *newPod.Spec.DeepCopy()
56015624
var newContainers []core.Container
@@ -5653,6 +5676,19 @@ func isPodResizeRequestSupported(pod core.Pod) bool {
56535676
return true
56545677
}
56555678

5679+
func resourcesRemoved(resourceList, oldResourceList core.ResourceList) bool {
5680+
if len(oldResourceList) > len(resourceList) {
5681+
return true
5682+
}
5683+
for name := range oldResourceList {
5684+
if _, ok := resourceList[name]; !ok {
5685+
return true
5686+
}
5687+
}
5688+
5689+
return false
5690+
}
5691+
56565692
// ValidatePodBinding tests if required fields in the pod binding are legal.
56575693
func ValidatePodBinding(binding *core.Binding) field.ErrorList {
56585694
allErrs := field.ErrorList{}

pkg/apis/core/validation/validation_test.go

+85-11
Original file line numberDiff line numberDiff line change
@@ -25463,6 +25463,8 @@ func TestValidateSELinuxChangePolicy(t *testing.T) {
2546325463
}
2546425464

2546525465
func TestValidatePodResize(t *testing.T) {
25466+
featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.SidecarContainers, true)
25467+
2546625468
mkPod := func(req, lim core.ResourceList, tweaks ...podtest.Tweak) *core.Pod {
2546725469
return podtest.MakePod("pod", append(tweaks,
2546825470
podtest.SetContainers(
@@ -25479,6 +25481,23 @@ func TestValidatePodResize(t *testing.T) {
2547925481
)...)
2548025482
}
2548125483

25484+
mkPodWithInitContainers := func(req, lim core.ResourceList, restartPolicy core.ContainerRestartPolicy, tweaks ...podtest.Tweak) *core.Pod {
25485+
return podtest.MakePod("pod", append(tweaks,
25486+
podtest.SetInitContainers(
25487+
podtest.MakeContainer(
25488+
"container",
25489+
podtest.SetContainerResources(
25490+
core.ResourceRequirements{
25491+
Requests: req,
25492+
Limits: lim,
25493+
},
25494+
),
25495+
podtest.SetContainerRestartPolicy(restartPolicy),
25496+
),
25497+
),
25498+
)...)
25499+
}
25500+
2548225501
tests := []struct {
2548325502
test string
2548425503
old *core.Pod
@@ -25672,20 +25691,10 @@ func TestValidatePodResize(t *testing.T) {
2567225691
old: mkPod(getResources("100m", "100Mi", "", ""), core.ResourceList{}),
2567325692
new: mkPod(getResources("100m", "100Mi", "", ""), getResources("200m", "200Mi", "", "")),
2567425693
err: "",
25675-
}, {
25676-
test: "Pod QoS unchanged, burstable -> burstable, remove limits",
25677-
old: mkPod(getResources("100m", "100Mi", "", ""), getResources("200m", "200Mi", "", "")),
25678-
new: mkPod(getResources("100m", "100Mi", "", ""), core.ResourceList{}),
25679-
err: "",
2568025694
}, {
2568125695
test: "Pod QoS unchanged, burstable -> burstable, add requests",
2568225696
old: mkPod(core.ResourceList{}, getResources("200m", "500Mi", "1Gi", "")),
25683-
new: mkPod(getResources("300m", "", "", ""), getResources("400m", "", "1Gi", "")),
25684-
err: "",
25685-
}, {
25686-
test: "Pod QoS unchanged, burstable -> burstable, remove requests",
25687-
old: mkPod(getResources("100m", "200Mi", "", ""), getResources("200m", "300Mi", "2Gi", "")),
25688-
new: mkPod(core.ResourceList{}, getResources("400m", "500Mi", "2Gi", "")),
25697+
new: mkPod(getResources("300m", "", "", ""), getResources("400m", "500Mi", "1Gi", "")),
2568925698
err: "",
2569025699
}, {
2569125700
test: "Pod QoS change, guaranteed -> burstable",
@@ -25717,6 +25726,71 @@ func TestValidatePodResize(t *testing.T) {
2571725726
old: mkPod(core.ResourceList{}, getResources("100m", "0", "1Gi", ""), podtest.SetOS(core.Windows)),
2571825727
new: mkPod(core.ResourceList{}, getResources("200m", "0", "1Gi", ""), podtest.SetOS(core.Windows)),
2571925728
err: "Forbidden: windows pods cannot be resized",
25729+
}, {
25730+
test: "Pod QoS unchanged, burstable -> burstable, remove cpu limit",
25731+
old: mkPod(core.ResourceList{}, getResources("100m", "100Mi", "", "")),
25732+
new: mkPod(core.ResourceList{}, getResources("", "100Mi", "", "")),
25733+
err: "spec.containers[0].resources.limits: Forbidden: resource limits cannot be removed",
25734+
}, {
25735+
test: "Pod QoS unchanged, burstable -> burstable, remove memory limit",
25736+
old: mkPod(core.ResourceList{}, getResources("100m", "100Mi", "", "")),
25737+
new: mkPod(core.ResourceList{}, getResources("100m", "", "", "")),
25738+
err: "spec.containers[0].resources.limits: Forbidden: resource limits cannot be removed",
25739+
}, {
25740+
test: "Pod QoS unchanged, burstable -> burstable, remove cpu request",
25741+
old: mkPod(getResources("100m", "100Mi", "", ""), core.ResourceList{}),
25742+
new: mkPod(getResources("", "100Mi", "", ""), core.ResourceList{}),
25743+
err: "spec.containers[0].resources.requests: Forbidden: resource requests cannot be removed",
25744+
}, {
25745+
test: "Pod QoS unchanged, burstable -> burstable, remove memory request",
25746+
old: mkPod(getResources("100m", "100Mi", "", ""), core.ResourceList{}),
25747+
new: mkPod(getResources("100m", "", "", ""), core.ResourceList{}),
25748+
err: "spec.containers[0].resources.requests: Forbidden: resource requests cannot be removed",
25749+
}, {
25750+
test: "Pod QoS unchanged, burstable -> burstable, remove cpu and memory limits",
25751+
old: mkPod(getResources("100m", "", "", ""), getResources("100m", "100Mi", "", "")),
25752+
new: mkPod(getResources("100m", "", "", ""), core.ResourceList{}),
25753+
err: "spec.containers[0].resources.limits: Forbidden: resource limits cannot be removed",
25754+
}, {
25755+
test: "Pod QoS unchanged, burstable -> burstable, remove cpu and memory requests",
25756+
old: mkPod(getResources("100m", "100Mi", "", ""), getResources("100m", "", "", "")),
25757+
new: mkPod(core.ResourceList{}, getResources("100m", "", "", "")),
25758+
err: "spec.containers[0].resources.requests: Forbidden: resource requests cannot be removed",
25759+
}, {
25760+
test: "Pod QoS unchanged, burstable -> burstable, remove cpu limit",
25761+
old: mkPodWithInitContainers(core.ResourceList{}, getResources("100m", "100Mi", "", ""), core.ContainerRestartPolicyAlways),
25762+
new: mkPodWithInitContainers(core.ResourceList{}, getResources("", "100Mi", "", ""), core.ContainerRestartPolicyAlways),
25763+
err: "spec.initContainers[0].resources.limits: Forbidden: resource limits cannot be removed",
25764+
}, {
25765+
test: "Pod QoS unchanged, burstable -> burstable, remove memory limit",
25766+
old: mkPodWithInitContainers(core.ResourceList{}, getResources("100m", "100Mi", "", ""), core.ContainerRestartPolicyAlways),
25767+
new: mkPodWithInitContainers(core.ResourceList{}, getResources("100m", "", "", ""), core.ContainerRestartPolicyAlways),
25768+
err: "spec.initContainers[0].resources.limits: Forbidden: resource limits cannot be removed",
25769+
}, {
25770+
test: "Pod QoS unchanged, burstable -> burstable, remove cpu request",
25771+
old: mkPodWithInitContainers(getResources("100m", "100Mi", "", ""), core.ResourceList{}, core.ContainerRestartPolicyAlways),
25772+
new: mkPodWithInitContainers(getResources("", "100Mi", "", ""), core.ResourceList{}, core.ContainerRestartPolicyAlways),
25773+
err: "spec.initContainers[0].resources.requests: Forbidden: resource requests cannot be removed",
25774+
}, {
25775+
test: "Pod QoS unchanged, burstable -> burstable, remove memory request",
25776+
old: mkPodWithInitContainers(getResources("100m", "100Mi", "", ""), core.ResourceList{}, core.ContainerRestartPolicyAlways),
25777+
new: mkPodWithInitContainers(getResources("100m", "", "", ""), core.ResourceList{}, core.ContainerRestartPolicyAlways),
25778+
err: "spec.initContainers[0].resources.requests: Forbidden: resource requests cannot be removed",
25779+
}, {
25780+
test: "Pod QoS unchanged, burstable -> burstable, remove cpu and memory limits",
25781+
old: mkPodWithInitContainers(getResources("100m", "", "", ""), getResources("100m", "100Mi", "", ""), core.ContainerRestartPolicyAlways),
25782+
new: mkPodWithInitContainers(getResources("100m", "", "", ""), core.ResourceList{}, core.ContainerRestartPolicyAlways),
25783+
err: "spec.initContainers[0].resources.limits: Forbidden: resource limits cannot be removed",
25784+
}, {
25785+
test: "Pod QoS unchanged, burstable -> burstable, remove cpu and memory requests",
25786+
old: mkPodWithInitContainers(getResources("100m", "100Mi", "", ""), getResources("100m", "", "", ""), core.ContainerRestartPolicyAlways),
25787+
new: mkPodWithInitContainers(core.ResourceList{}, getResources("100m", "", "", ""), core.ContainerRestartPolicyAlways),
25788+
err: "spec.initContainers[0].resources.requests: Forbidden: resource requests cannot be removed",
25789+
}, {
25790+
test: "Pod QoS unchanged, burstable -> burstable, remove cpu and memory requests",
25791+
old: mkPodWithInitContainers(getResources("100m", "100Mi", "", ""), getResources("100m", "", "", ""), ""),
25792+
new: mkPodWithInitContainers(core.ResourceList{}, getResources("100m", "", "", ""), ""),
25793+
err: "spec: Forbidden: only cpu and memory resources are mutable",
2572025794
},
2572125795
{
2572225796
test: "Pod with nil Resource field in Status",

test/e2e/common/node/pod_resize.go

+79-2
Original file line numberDiff line numberDiff line change
@@ -919,13 +919,89 @@ func doPodResizeErrorTests(f *framework.Framework) {
919919
patchString: `{"spec":{"containers":[
920920
{"name":"c1", "resources":{"requests":{"memory":"400Mi"}}}
921921
]}}`,
922-
patchError: "Pod QoS is immutable",
922+
patchError: "Pod QOS Class may not change as a result of resizing",
923923
expected: []e2epod.ResizableContainerInfo{
924924
{
925925
Name: "c1",
926926
},
927927
},
928928
},
929+
{
930+
name: "Burstable QoS pod, one container with cpu & memory requests + limits - remove memory limits",
931+
containers: []e2epod.ResizableContainerInfo{
932+
{
933+
Name: "c1",
934+
Resources: &e2epod.ContainerResources{CPUReq: "200m", CPULim: "400m", MemReq: "250Mi", MemLim: "500Mi"},
935+
},
936+
},
937+
patchString: `{"spec":{"containers":[
938+
{"name":"c1", "resources":{"limits":{"memory": null}}}
939+
]}}`,
940+
patchError: "resource limits cannot be removed",
941+
expected: []e2epod.ResizableContainerInfo{
942+
{
943+
Name: "c1",
944+
Resources: &e2epod.ContainerResources{CPUReq: "200m", CPULim: "400m", MemReq: "250Mi", MemLim: "500Mi"},
945+
},
946+
},
947+
},
948+
{
949+
name: "Burstable QoS pod, one container with cpu & memory requests + limits - remove CPU limits",
950+
containers: []e2epod.ResizableContainerInfo{
951+
{
952+
Name: "c1",
953+
Resources: &e2epod.ContainerResources{CPUReq: "200m", CPULim: "400m", MemReq: "250Mi", MemLim: "500Mi"},
954+
},
955+
},
956+
patchString: `{"spec":{"containers":[
957+
{"name":"c1", "resources":{"limits":{"cpu": null}}}
958+
]}}`,
959+
patchError: "resource limits cannot be removed",
960+
expected: []e2epod.ResizableContainerInfo{
961+
{
962+
Name: "c1",
963+
Resources: &e2epod.ContainerResources{CPUReq: "200m", CPULim: "400m", MemReq: "250Mi", MemLim: "500Mi"},
964+
},
965+
},
966+
},
967+
{
968+
name: "Burstable QoS pod, one container with memory requests + limits, cpu requests - remove CPU requests",
969+
containers: []e2epod.ResizableContainerInfo{
970+
{
971+
Name: "c1",
972+
Resources: &e2epod.ContainerResources{CPUReq: "200m", MemReq: "250Mi", MemLim: "500Mi"},
973+
},
974+
},
975+
patchString: `{"spec":{"containers":[
976+
{"name":"c1", "resources":{"requests":{"cpu": null}}}
977+
]}}`,
978+
patchError: "resource requests cannot be removed",
979+
expected: []e2epod.ResizableContainerInfo{
980+
{
981+
Name: "c1",
982+
Resources: &e2epod.ContainerResources{CPUReq: "200m", MemReq: "250Mi", MemLim: "500Mi"},
983+
},
984+
},
985+
},
986+
{
987+
name: "Burstable QoS pod, one container with CPU requests + limits, cpu requests - remove memory requests",
988+
containers: []e2epod.ResizableContainerInfo{
989+
{
990+
Name: "c1",
991+
Resources: &e2epod.ContainerResources{CPUReq: "200m", CPULim: "400m", MemReq: "250Mi"},
992+
},
993+
},
994+
patchString: `{"spec":{"containers":[
995+
{"name":"c1", "resources":{"requests":{"memory": null}}}
996+
]}}`,
997+
patchError: "resource requests cannot be removed",
998+
expected: []e2epod.ResizableContainerInfo{
999+
{
1000+
Name: "c1",
1001+
Resources: &e2epod.ContainerResources{CPUReq: "200m", CPULim: "400m", MemReq: "250Mi"},
1002+
},
1003+
},
1004+
},
9291005
}
9301006

9311007
timeouts := f.Timeouts
@@ -959,7 +1035,8 @@ func doPodResizeErrorTests(f *framework.Framework) {
9591035
if tc.patchError == "" {
9601036
framework.ExpectNoError(pErr, "failed to patch pod for resize")
9611037
} else {
962-
gomega.Expect(pErr).To(gomega.HaveOccurred(), tc.patchError)
1038+
gomega.Expect(pErr).To(gomega.HaveOccurred())
1039+
gomega.Expect(pErr.Error()).To(gomega.ContainSubstring(tc.patchError))
9631040
patchedPod = newPod
9641041
}
9651042

0 commit comments

Comments
 (0)