Skip to content

Commit 202579b

Browse files
committed
Add e2e tests for CPU startup boost
1 parent 18d8efd commit 202579b

File tree

4 files changed

+333
-5
lines changed

4 files changed

+333
-5
lines changed

vertical-pod-autoscaler/e2e/v1/admission_controller.go

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1098,6 +1098,122 @@ var _ = AdmissionControllerE2eDescribe("Admission-controller", func() {
10981098
})
10991099
})
11001100

1101+
var _ = AdmissionControllerE2eDescribe("Admission-controller", func() {
1102+
f := framework.NewDefaultFramework("vertical-pod-autoscaling")
1103+
f.NamespacePodSecurityEnforceLevel = podsecurity.LevelBaseline
1104+
1105+
ginkgo.BeforeEach(func() {
1106+
waitForVpaWebhookRegistration(f)
1107+
})
1108+
1109+
f.It("boosts CPU by factor on pod creation", framework.WithFeatureGate(features.CPUStartupBoost), func() {
1110+
initialCPU := ParseQuantityOrDie("100m")
1111+
expectedCPU := ParseQuantityOrDie("200m")
1112+
d := NewHamsterDeploymentWithResources(f, initialCPU, ParseQuantityOrDie("100Mi"))
1113+
1114+
ginkgo.By("Setting up a VPA with a startup boost policy (factor)")
1115+
containerName := utils.GetHamsterContainerNameByIndex(0)
1116+
factor := int32(2)
1117+
vpaCRD := test.VerticalPodAutoscaler().
1118+
WithName("hamster-vpa").
1119+
WithNamespace(f.Namespace.Name).
1120+
WithTargetRef(utils.HamsterTargetRef).
1121+
WithContainer(containerName).
1122+
WithCPUStartupBoost(vpa_types.FactorStartupBoostType, &factor, nil, "15s").
1123+
AppendRecommendation(
1124+
test.Recommendation().
1125+
WithContainer(containerName).
1126+
WithTarget("100m", "100Mi").
1127+
GetContainerResources(),
1128+
).
1129+
Get()
1130+
utils.InstallVPA(f, vpaCRD)
1131+
1132+
ginkgo.By("Starting the deployment and verifying the pod is boosted")
1133+
podList := utils.StartDeploymentPods(f, d)
1134+
pod := podList.Items[0]
1135+
gomega.Expect(pod.Spec.Containers[0].Resources.Requests.Cpu().Cmp(expectedCPU)).To(gomega.Equal(0))
1136+
gomega.Expect(pod.Spec.Containers[0].Resources.Limits.Cpu().Cmp(expectedCPU)).To(gomega.Equal(0))
1137+
})
1138+
1139+
f.It("boosts CPU by quantity on pod creation", framework.WithFeatureGate(features.CPUStartupBoost), func() {
1140+
initialCPU := ParseQuantityOrDie("100m")
1141+
boostCPUQuantity := ParseQuantityOrDie("500m")
1142+
expectedCPU := ParseQuantityOrDie("600m")
1143+
d := NewHamsterDeploymentWithResources(f, initialCPU, ParseQuantityOrDie("100Mi"))
1144+
1145+
ginkgo.By("Setting up a VPA with a startup boost policy (quantity)")
1146+
containerName := utils.GetHamsterContainerNameByIndex(0)
1147+
vpaCRD := test.VerticalPodAutoscaler().
1148+
WithName("hamster-vpa").
1149+
WithNamespace(f.Namespace.Name).
1150+
WithTargetRef(utils.HamsterTargetRef).
1151+
WithContainer(containerName).
1152+
WithCPUStartupBoost(vpa_types.QuantityStartupBoostType, nil, &boostCPUQuantity, "15s").
1153+
AppendRecommendation(
1154+
test.Recommendation().
1155+
WithContainer(containerName).
1156+
WithTarget("100m", "100Mi").
1157+
GetContainerResources(),
1158+
).
1159+
Get()
1160+
utils.InstallVPA(f, vpaCRD)
1161+
1162+
ginkgo.By("Starting the deployment and verifying the pod is boosted")
1163+
podList := utils.StartDeploymentPods(f, d)
1164+
pod := podList.Items[0]
1165+
gomega.Expect(pod.Spec.Containers[0].Resources.Requests.Cpu().Cmp(expectedCPU)).To(gomega.Equal(0))
1166+
gomega.Expect(pod.Spec.Containers[0].Resources.Limits.Cpu().Cmp(expectedCPU)).To(gomega.Equal(0))
1167+
})
1168+
1169+
f.It("boosts CPU on pod creation when VPA update mode is Off", framework.WithFeatureGate(features.CPUStartupBoost), func() {
1170+
initialCPU := ParseQuantityOrDie("100m")
1171+
expectedCPU := ParseQuantityOrDie("200m")
1172+
d := NewHamsterDeploymentWithResources(f, initialCPU, ParseQuantityOrDie("100Mi"))
1173+
1174+
ginkgo.By("Setting up a VPA with updateMode Off and a startup boost policy")
1175+
containerName := utils.GetHamsterContainerNameByIndex(0)
1176+
factor := int32(2)
1177+
vpaCRD := test.VerticalPodAutoscaler().
1178+
WithName("hamster-vpa").
1179+
WithNamespace(f.Namespace.Name).
1180+
WithTargetRef(utils.HamsterTargetRef).
1181+
WithContainer(containerName).
1182+
WithUpdateMode(vpa_types.UpdateModeOff). // VPA is off, but boost should still work
1183+
WithCPUStartupBoost(vpa_types.FactorStartupBoostType, &factor, nil, "15s").
1184+
Get()
1185+
utils.InstallVPA(f, vpaCRD)
1186+
1187+
ginkgo.By("Starting the deployment and verifying the pod is boosted")
1188+
podList := utils.StartDeploymentPods(f, d)
1189+
pod := podList.Items[0]
1190+
gomega.Expect(pod.Spec.Containers[0].Resources.Requests.Cpu().Cmp(expectedCPU)).To(gomega.Equal(0))
1191+
})
1192+
1193+
f.It("doesn't boost CPU on pod creation when scaling mode is Off", framework.WithFeatureGate(features.CPUStartupBoost), func() {
1194+
initialCPU := ParseQuantityOrDie("100m")
1195+
d := NewHamsterDeploymentWithResources(f, initialCPU, ParseQuantityOrDie("100Mi"))
1196+
1197+
ginkgo.By("Setting up a VPA with a startup boost policy and scaling mode Off")
1198+
containerName := utils.GetHamsterContainerNameByIndex(0)
1199+
factor := int32(2)
1200+
vpaCRD := test.VerticalPodAutoscaler().
1201+
WithName("hamster-vpa").
1202+
WithNamespace(f.Namespace.Name).
1203+
WithTargetRef(utils.HamsterTargetRef).
1204+
WithContainer(containerName).
1205+
WithCPUStartupBoost(vpa_types.FactorStartupBoostType, &factor, nil, "15s").
1206+
WithScalingMode(containerName, vpa_types.ContainerScalingModeOff).
1207+
Get()
1208+
utils.InstallVPA(f, vpaCRD)
1209+
1210+
ginkgo.By("Starting the deployment and verifying the pod is NOT boosted")
1211+
podList := utils.StartDeploymentPods(f, d)
1212+
pod := podList.Items[0]
1213+
gomega.Expect(pod.Spec.Containers[0].Resources.Requests.Cpu().Cmp(initialCPU)).To(gomega.Equal(0))
1214+
})
1215+
})
1216+
11011217
func waitForVpaWebhookRegistration(f *framework.Framework) {
11021218
ginkgo.By("Waiting for VPA webhook registration")
11031219
gomega.Eventually(func() bool {

vertical-pod-autoscaler/e2e/v1/common.go

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -244,14 +244,30 @@ func InstallRawVPA(f *framework.Framework, obj interface{}) error {
244244

245245
// AnnotatePod adds annotation for an existing pod.
246246
func AnnotatePod(f *framework.Framework, podName, annotationName, annotationValue string) {
247-
bytes, err := json.Marshal([]utils.PatchRecord{{
247+
pod, err := f.ClientSet.CoreV1().Pods(f.Namespace.Name).Get(context.TODO(), podName, metav1.GetOptions{})
248+
gomega.Expect(err).NotTo(gomega.HaveOccurred(), "Failed to get pod.")
249+
250+
patches := []utils.PatchRecord{}
251+
if pod.Annotations == nil {
252+
patches = append(patches, utils.PatchRecord{
253+
Op: "add",
254+
Path: "/metadata/annotations",
255+
Value: make(map[string]string),
256+
})
257+
}
258+
259+
patches = append(patches, utils.PatchRecord{
248260
Op: "add",
249-
Path: fmt.Sprintf("/metadata/annotations/%v", annotationName),
261+
Path: fmt.Sprintf("/metadata/annotations/%s", annotationName),
250262
Value: annotationValue,
251-
}})
252-
pod, err := f.ClientSet.CoreV1().Pods(f.Namespace.Name).Patch(context.TODO(), podName, types.JSONPatchType, bytes, metav1.PatchOptions{})
263+
})
264+
265+
bytes, err := json.Marshal(patches)
266+
gomega.Expect(err).NotTo(gomega.HaveOccurred())
267+
268+
patchedPod, err := f.ClientSet.CoreV1().Pods(f.Namespace.Name).Patch(context.TODO(), podName, types.JSONPatchType, bytes, metav1.PatchOptions{})
253269
gomega.Expect(err).NotTo(gomega.HaveOccurred(), "Failed to patch pod.")
254-
gomega.Expect(pod.Annotations[annotationName]).To(gomega.Equal(annotationValue))
270+
gomega.Expect(patchedPod.Annotations[annotationName]).To(gomega.Equal(annotationValue))
255271
}
256272

257273
// ParseQuantityOrDie parses quantity from string and dies with an error if

vertical-pod-autoscaler/e2e/v1/full_vpa.go

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -356,6 +356,121 @@ var _ = FullVpaE2eDescribe("Pods under VPA with non-recognized recommender expli
356356
})
357357
})
358358

359+
var _ = FullVpaE2eDescribe("Pods under VPA with CPUStartupBoost", func() {
360+
var (
361+
rc *ResourceConsumer
362+
)
363+
replicas := 3
364+
365+
ginkgo.AfterEach(func() {
366+
rc.CleanUp()
367+
})
368+
369+
f := framework.NewDefaultFramework("vertical-pod-autoscaling")
370+
f.NamespacePodSecurityEnforceLevel = podsecurity.LevelBaseline
371+
372+
ginkgo.Describe("have CPU startup boost recommendation applied", func() {
373+
ginkgo.BeforeEach(func() {
374+
waitForVpaWebhookRegistration(f)
375+
})
376+
377+
f.It("to all containers of a pod", framework.WithFeatureGate(features.CPUStartupBoost), func() {
378+
ns := f.Namespace.Name
379+
ginkgo.By("Setting up a VPA CRD with CPUStartupBoost")
380+
targetRef := &autoscaling.CrossVersionObjectReference{
381+
APIVersion: "apps/v1",
382+
Kind: "Deployment",
383+
Name: "hamster",
384+
}
385+
386+
containerName := utils.GetHamsterContainerNameByIndex(0)
387+
factor := int32(100)
388+
vpaCRD := test.VerticalPodAutoscaler().
389+
WithName("hamster-vpa").
390+
WithNamespace(f.Namespace.Name).
391+
WithTargetRef(targetRef).
392+
WithUpdateMode(vpa_types.UpdateModeInPlaceOrRecreate).
393+
WithContainer(containerName).
394+
WithCPUStartupBoost(vpa_types.FactorStartupBoostType, &factor, nil, "10s").
395+
Get()
396+
utils.InstallVPA(f, vpaCRD)
397+
398+
ginkgo.By("Setting up a hamster deployment")
399+
rc = NewDynamicResourceConsumer("hamster", ns, KindDeployment,
400+
replicas,
401+
1, /*initCPUTotal*/
402+
10, /*initMemoryTotal*/
403+
1, /*initCustomMetric*/
404+
initialCPU, /*cpuRequest*/
405+
initialMemory, /*memRequest*/
406+
f.ClientSet,
407+
f.ScalesGetter)
408+
409+
// Pods should be created with boosted CPU (10m * 100 = 1000m)
410+
err := waitForResourceRequestInRangeInPods(
411+
f, utils.PollTimeout, metav1.ListOptions{LabelSelector: "name=hamster"}, apiv1.ResourceCPU,
412+
ParseQuantityOrDie("900m"), ParseQuantityOrDie("1100m"))
413+
gomega.Expect(err).NotTo(gomega.HaveOccurred())
414+
415+
// Pods should be scaled back down in-place after they become Ready and
416+
// StartupBoost.CPU.Duration has elapsed
417+
err = waitForResourceRequestInRangeInPods(
418+
f, utils.PollTimeout, metav1.ListOptions{LabelSelector: "name=hamster"}, apiv1.ResourceCPU,
419+
ParseQuantityOrDie(minimalCPULowerBound), ParseQuantityOrDie(minimalCPUUpperBound))
420+
gomega.Expect(err).NotTo(gomega.HaveOccurred())
421+
})
422+
423+
f.It("to a subset of containers in a pod", framework.WithFeatureGate(features.CPUStartupBoost), func() {
424+
ns := f.Namespace.Name
425+
426+
ginkgo.By("Setting up a VPA CRD with CPUStartupBoost")
427+
targetRef := &autoscaling.CrossVersionObjectReference{
428+
APIVersion: "apps/v1",
429+
Kind: "Deployment",
430+
Name: "hamster",
431+
}
432+
433+
containerName := utils.GetHamsterContainerNameByIndex(0)
434+
factor := int32(100)
435+
vpaCRD := test.VerticalPodAutoscaler().
436+
WithName("hamster-vpa").
437+
WithNamespace(f.Namespace.Name).
438+
WithTargetRef(targetRef).
439+
WithUpdateMode(vpa_types.UpdateModeInPlaceOrRecreate).
440+
WithContainer(containerName).
441+
WithCPUStartupBoost(vpa_types.FactorStartupBoostType, &factor, nil, "10s").
442+
Get()
443+
444+
utils.InstallVPA(f, vpaCRD)
445+
446+
ginkgo.By("Setting up a hamster deployment")
447+
rc = NewDynamicResourceConsumer("hamster", ns, KindDeployment,
448+
replicas,
449+
1, /*initCPUTotal*/
450+
10, /*initMemoryTotal*/
451+
1, /*initCustomMetric*/
452+
initialCPU, /*cpuRequest*/
453+
initialMemory, /*memRequest*/
454+
f.ClientSet,
455+
f.ScalesGetter)
456+
457+
// Pods should be created with boosted CPU (10m * 100 = 1000m)
458+
err := waitForResourceRequestInRangeInPods(
459+
f, utils.PollTimeout, metav1.ListOptions{LabelSelector: "name=hamster"}, apiv1.ResourceCPU,
460+
ParseQuantityOrDie("900m"), ParseQuantityOrDie("1100m"))
461+
gomega.Expect(err).NotTo(gomega.HaveOccurred())
462+
463+
// Pods should be scaled back down in-place after they become Ready and
464+
// StartupBoost.CPU.Duration has elapsed
465+
err = waitForResourceRequestInRangeInPods(
466+
f, utils.PollTimeout, metav1.ListOptions{LabelSelector: "name=hamster"}, apiv1.ResourceCPU,
467+
ParseQuantityOrDie(minimalCPULowerBound), ParseQuantityOrDie(minimalCPUUpperBound))
468+
gomega.Expect(err).NotTo(gomega.HaveOccurred())
469+
})
470+
})
471+
472+
})
473+
359474
var _ = FullVpaE2eDescribe("OOMing pods under VPA", func() {
360475
const replicas = 3
361476

vertical-pod-autoscaler/e2e/v1/updater.go

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ import (
2626
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2727
"k8s.io/autoscaler/vertical-pod-autoscaler/e2e/utils"
2828
vpa_types "k8s.io/autoscaler/vertical-pod-autoscaler/pkg/apis/autoscaling.k8s.io/v1"
29+
"k8s.io/autoscaler/vertical-pod-autoscaler/pkg/features"
30+
"k8s.io/autoscaler/vertical-pod-autoscaler/pkg/utils/annotations"
2931
"k8s.io/autoscaler/vertical-pod-autoscaler/pkg/utils/status"
3032
"k8s.io/autoscaler/vertical-pod-autoscaler/pkg/utils/test"
3133
"k8s.io/kubernetes/test/e2e/framework"
@@ -207,6 +209,85 @@ var _ = UpdaterE2eDescribe("Updater", func() {
207209
})
208210
})
209211

212+
var _ = UpdaterE2eDescribe("Updater", func() {
213+
f := framework.NewDefaultFramework("vertical-pod-autoscaling")
214+
f.NamespacePodSecurityEnforceLevel = podsecurity.LevelBaseline
215+
216+
f.It("Unboost pods when they become Ready", framework.WithFeatureGate(features.CPUStartupBoost), func() {
217+
const statusUpdateInterval = 10 * time.Second
218+
219+
ginkgo.By("Setting up the Admission Controller status")
220+
stopCh := make(chan struct{})
221+
statusUpdater := status.NewUpdater(
222+
f.ClientSet,
223+
status.AdmissionControllerStatusName,
224+
status.AdmissionControllerStatusNamespace,
225+
statusUpdateInterval,
226+
"e2e test",
227+
)
228+
defer func() {
229+
// Schedule a cleanup of the Admission Controller status.
230+
// Status is created outside the test namespace.
231+
ginkgo.By("Deleting the Admission Controller status")
232+
close(stopCh)
233+
err := f.ClientSet.CoordinationV1().Leases(status.AdmissionControllerStatusNamespace).
234+
Delete(context.TODO(), status.AdmissionControllerStatusName, metav1.DeleteOptions{})
235+
gomega.Expect(err).NotTo(gomega.HaveOccurred())
236+
}()
237+
statusUpdater.Run(stopCh)
238+
239+
podList := setupPodsForCPUBoost(f, "100m", "100Mi")
240+
initialPods := podList.DeepCopy()
241+
242+
ginkgo.By("Waiting for pods to be in-place updated")
243+
err := WaitForPodsUpdatedWithoutEviction(f, initialPods)
244+
gomega.Expect(err).NotTo(gomega.HaveOccurred())
245+
})
246+
247+
})
248+
249+
func setupPodsForCPUBoost(f *framework.Framework, hamsterCPU, hamsterMemory string) *apiv1.PodList {
250+
controller := &autoscaling.CrossVersionObjectReference{
251+
APIVersion: "apps/v1",
252+
Kind: "Deployment",
253+
Name: "hamster-deployment",
254+
}
255+
ginkgo.By(fmt.Sprintf("Setting up a hamster %v", controller.Kind))
256+
// Create pods with boosted CPU, which is 2x the target recommendation
257+
boostedCPU := "200m"
258+
setupHamsterController(f, controller.Kind, boostedCPU, hamsterMemory, utils.DefaultHamsterReplicas)
259+
podList, err := GetHamsterPods(f)
260+
gomega.Expect(err).NotTo(gomega.HaveOccurred())
261+
262+
ginkgo.By("Setting up a VPA CRD")
263+
containerName := utils.GetHamsterContainerNameByIndex(0)
264+
factor := int32(2)
265+
vpaCRD := test.VerticalPodAutoscaler().
266+
WithName("hamster-vpa").
267+
WithNamespace(f.Namespace.Name).
268+
WithTargetRef(controller).
269+
WithUpdateMode(vpa_types.UpdateModeAuto).
270+
WithContainer(containerName).
271+
WithCPUStartupBoost(vpa_types.FactorStartupBoostType, &factor, nil, "1s").
272+
AppendRecommendation(
273+
test.Recommendation().
274+
WithContainer(containerName).
275+
WithTarget(hamsterCPU, hamsterMemory).
276+
GetContainerResources(),
277+
).
278+
Get()
279+
280+
utils.InstallVPA(f, vpaCRD)
281+
282+
ginkgo.By("Annotating pods with boost annotation")
283+
for _, pod := range podList.Items {
284+
original, err := annotations.GetOriginalResourcesAnnotationValue(&pod.Spec.Containers[0])
285+
gomega.Expect(err).NotTo(gomega.HaveOccurred())
286+
AnnotatePod(f, pod.Name, annotations.StartupCPUBoostAnnotation, original)
287+
}
288+
return podList
289+
}
290+
210291
func setupPodsForUpscalingEviction(f *framework.Framework) *apiv1.PodList {
211292
return setupPodsForEviction(f, "100m", "100Mi", nil)
212293
}

0 commit comments

Comments
 (0)