Skip to content

Commit 6ea7337

Browse files
committed
VPA: Add in-place actuation and admission-controller e2e tests
Signed-off-by: Max Cao <[email protected]>
1 parent d8b804c commit 6ea7337

File tree

3 files changed

+394
-9
lines changed

3 files changed

+394
-9
lines changed

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

Lines changed: 348 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,346 @@ var _ = ActuationSuiteE2eDescribe("Actuation", func() {
167167
gomega.Expect(foundUpdated).To(gomega.Equal(1))
168168
})
169169

170+
ginkgo.It("still applies recommendations on restart when update mode is InPlaceOrRecreate", func() {
171+
ginkgo.By("Setting up a hamster deployment")
172+
SetupHamsterDeployment(f, "100m", "100Mi", defaultHamsterReplicas)
173+
podList, err := GetHamsterPods(f)
174+
gomega.Expect(err).NotTo(gomega.HaveOccurred())
175+
podSet := MakePodSet(podList)
176+
177+
ginkgo.By("Setting up a VPA CRD in mode InPlaceOrRecreate")
178+
containerName := GetHamsterContainerNameByIndex(0)
179+
vpaCRD := test.VerticalPodAutoscaler().
180+
WithName("hamster-vpa").
181+
WithNamespace(f.Namespace.Name).
182+
WithTargetRef(hamsterTargetRef).
183+
WithUpdateMode(vpa_types.UpdateModeInPlaceOrRecreate).
184+
WithContainer(containerName).
185+
AppendRecommendation(
186+
test.Recommendation().
187+
WithContainer(containerName).
188+
WithTarget("200m", "").
189+
WithLowerBound("200m", "").
190+
WithUpperBound("200m", "").
191+
GetContainerResources()).
192+
Get()
193+
194+
InstallVPA(f, vpaCRD)
195+
updatedCPURequest := ParseQuantityOrDie("200m")
196+
197+
ginkgo.By(fmt.Sprintf("Waiting for pods to be evicted, hoping it won't happen, sleep for %s", VpaEvictionTimeout.String()))
198+
CheckNoPodsEvicted(f, podSet)
199+
ginkgo.By("Forcefully killing one pod")
200+
killPod(f, podList)
201+
202+
ginkgo.By("Checking that request was modified after forceful restart")
203+
updatedPodList, _ := GetHamsterPods(f)
204+
var foundUpdated int32
205+
for _, pod := range updatedPodList.Items {
206+
podRequest := getCPURequest(pod.Spec)
207+
framework.Logf("podReq: %v", podRequest)
208+
if podRequest.Cmp(updatedCPURequest) == 0 {
209+
foundUpdated += 1
210+
}
211+
}
212+
gomega.Expect(foundUpdated).To(gomega.Equal(defaultHamsterReplicas))
213+
})
214+
215+
// TODO(maxcao13): disruptionless means no container/pod restart; however NotRequired policy does not guarantee this...
216+
ginkgo.It("applies disruptionless in-place updates to all containers where request is within bounds when update mode is InPlaceOrRecreate", func() {
217+
ginkgo.By("Setting up a hamster deployment with all containers with NotRequired resize policies")
218+
replicas := int32(2)
219+
expectedContainerRestarts := int32(0)
220+
SetupHamsterDeployment(f, "100m", "100Mi", replicas)
221+
podList, err := GetHamsterPods(f)
222+
gomega.Expect(err).NotTo(gomega.HaveOccurred())
223+
podSet := MakePodSet(podList)
224+
225+
ginkgo.By("Setting up a VPA CRD")
226+
containerName := GetHamsterContainerNameByIndex(0)
227+
updatedCPU := "250m"
228+
vpaCRD := test.VerticalPodAutoscaler().
229+
WithName("hamster-vpa").
230+
WithNamespace(f.Namespace.Name).
231+
WithTargetRef(hamsterTargetRef).
232+
WithContainer(containerName).
233+
WithUpdateMode(vpa_types.UpdateModeInPlaceOrRecreate).
234+
AppendRecommendation(
235+
test.Recommendation().
236+
WithContainer(containerName).
237+
WithTarget(updatedCPU, "200Mi").
238+
WithLowerBound("100m", "100Mi").
239+
WithUpperBound("300m", "250Mi").
240+
GetContainerResources()).
241+
Get()
242+
243+
InstallVPA(f, vpaCRD)
244+
245+
ginkgo.By(fmt.Sprintf("Waiting for pods to be evicted, hoping it won't happen, sleep for %s", VpaEvictionTimeout.String()))
246+
CheckNoPodsEvicted(f, podSet)
247+
248+
ginkgo.By("Checking that request was modified after a while due to in-place update, and was disruptionless")
249+
updatedPodList, _ := GetHamsterPods(f)
250+
var foundUpdated, foundContainerRestarts int32 = 0, 0
251+
for _, pod := range updatedPodList.Items {
252+
podRequest := getCPURequest(pod.Spec)
253+
containerRestarts := getContainerRestarts(pod.Status)
254+
framework.Logf("podReq: %v, containerRestarts: %v", podRequest, getContainerRestarts(pod.Status))
255+
if podRequest.Cmp(ParseQuantityOrDie(updatedCPU)) == 0 {
256+
foundUpdated += 1
257+
}
258+
foundContainerRestarts += containerRestarts
259+
}
260+
gomega.Expect(foundUpdated).To(gomega.Equal(replicas))
261+
gomega.Expect(foundContainerRestarts).To(gomega.Equal(expectedContainerRestarts))
262+
})
263+
264+
ginkgo.It("applies partial disruptionless in-place updates to a pod when request is within bounds when update mode is InPlaceOrRecreate", func() {
265+
ginkgo.By("Setting up a hamster deployment with first container using RestartContainer resize policies")
266+
cpuQuantity := ParseQuantityOrDie("100m")
267+
memoryQuantity := ParseQuantityOrDie("100Mi")
268+
replicas := int32(2)
269+
containers := 2
270+
expectedContainerRestarts := int32(1) * replicas // 1 container restart per pod
271+
d := NewNHamstersDeployment(f, containers)
272+
d.Spec.Template.Spec.Containers[0].Resources.Requests = apiv1.ResourceList{
273+
apiv1.ResourceCPU: cpuQuantity,
274+
apiv1.ResourceMemory: memoryQuantity,
275+
}
276+
d.Spec.Replicas = &replicas
277+
d.Spec.Template.Spec.Containers[0].ResizePolicy = []apiv1.ContainerResizePolicy{
278+
{
279+
ResourceName: apiv1.ResourceCPU,
280+
RestartPolicy: apiv1.RestartContainer,
281+
},
282+
{
283+
ResourceName: apiv1.ResourceMemory,
284+
RestartPolicy: apiv1.RestartContainer,
285+
},
286+
}
287+
d, err := f.ClientSet.AppsV1().Deployments(f.Namespace.Name).Create(context.TODO(), d, metav1.CreateOptions{})
288+
gomega.Expect(err).NotTo(gomega.HaveOccurred(), "unexpected error when starting deployment creation")
289+
err = framework_deployment.WaitForDeploymentComplete(f.ClientSet, d)
290+
gomega.Expect(err).NotTo(gomega.HaveOccurred(), "unexpected error waiting for deployment creation to finish")
291+
292+
podList, err := GetHamsterPods(f)
293+
gomega.Expect(err).NotTo(gomega.HaveOccurred())
294+
podSet := MakePodSet(podList)
295+
296+
ginkgo.By("Setting up a VPA CRD")
297+
containerName := GetHamsterContainerNameByIndex(0)
298+
updatedCPU := "250m"
299+
vpaCRD := test.VerticalPodAutoscaler().
300+
WithName("hamster-vpa").
301+
WithNamespace(f.Namespace.Name).
302+
WithTargetRef(hamsterTargetRef).
303+
WithContainer(containerName).
304+
WithUpdateMode(vpa_types.UpdateModeInPlaceOrRecreate).
305+
AppendRecommendation(
306+
test.Recommendation().
307+
WithContainer(containerName).
308+
WithTarget(updatedCPU, "200Mi").
309+
WithLowerBound("100m", "100Mi").
310+
WithUpperBound("300m", "250Mi").
311+
GetContainerResources()).
312+
Get()
313+
314+
InstallVPA(f, vpaCRD)
315+
316+
ginkgo.By(fmt.Sprintf("Waiting for pods to be evicted, hoping it won't happen, sleep for %s", VpaEvictionTimeout.String()))
317+
CheckNoPodsEvicted(f, podSet)
318+
319+
ginkgo.By("Checking that request was modified after a while due to in-place update, and was partially disruptionless")
320+
updatedPodList, _ := GetHamsterPods(f)
321+
var foundUpdated, foundContainerRestarts int32 = 0, 0
322+
for _, pod := range updatedPodList.Items {
323+
podRequest := getCPURequest(pod.Spec)
324+
containerRestarts := getContainerRestarts(pod.Status)
325+
framework.Logf("podReq: %v, containerRestarts: %v", podRequest, getContainerRestarts(pod.Status))
326+
if podRequest.Cmp(ParseQuantityOrDie(updatedCPU)) == 0 {
327+
foundUpdated += 1
328+
}
329+
foundContainerRestarts += containerRestarts
330+
}
331+
gomega.Expect(foundUpdated).To(gomega.Equal(replicas))
332+
gomega.Expect(foundContainerRestarts).To(gomega.Equal(expectedContainerRestarts))
333+
})
334+
335+
// TODO(maxcao13): To me how this test fits in the test plan does not make sense. Whether the container is
336+
// able to be disrupted is not determined by the VPA but is by the kubelet, so why would we force the container
337+
// to restart if the request is out of the recommendation bounds? I must be missing something...
338+
ginkgo.It("applies disruptive in-place updates to a container in all pods when request out of bounds when update mode is InPlaceOrRecreate", func() {
339+
ginkgo.Skip("This test should be re-enabled once we have a better understanding of how to test this scenario")
340+
341+
ginkgo.By("Setting up a hamster deployment with a container using NotRequired resize policies")
342+
replicas := int32(2)
343+
expectedContainerRestarts := int32(1) * replicas // 1 container restart per pod
344+
SetupHamsterDeployment(f, "1", "1Gi", replicas) // outside recommendation range
345+
podList, err := GetHamsterPods(f)
346+
gomega.Expect(err).NotTo(gomega.HaveOccurred())
347+
podSet := MakePodSet(podList)
348+
349+
ginkgo.By("Setting up a VPA CRD")
350+
containerName := GetHamsterContainerNameByIndex(0)
351+
updatedCPU := "250m"
352+
vpaCRD := test.VerticalPodAutoscaler().
353+
WithName("hamster-vpa").
354+
WithNamespace(f.Namespace.Name).
355+
WithTargetRef(hamsterTargetRef).
356+
WithContainer(containerName).
357+
WithUpdateMode(vpa_types.UpdateModeInPlaceOrRecreate).
358+
AppendRecommendation(
359+
test.Recommendation().
360+
WithContainer(containerName).
361+
WithTarget(updatedCPU, "200Mi").
362+
WithLowerBound("150m", "150Mi").
363+
WithUpperBound("300m", "250Mi").
364+
GetContainerResources()).
365+
Get()
366+
367+
InstallVPA(f, vpaCRD)
368+
369+
ginkgo.By(fmt.Sprintf("Waiting for pods to be evicted, hoping it won't happen, sleep for %s", VpaEvictionTimeout.String()))
370+
CheckNoPodsEvicted(f, podSet)
371+
372+
ginkgo.By("Checking that request was modified after a while due to in-place update, and was disruptionless")
373+
updatedPodList, _ := GetHamsterPods(f)
374+
var foundUpdated, foundContainerRestarts int32 = 0, 0
375+
for _, pod := range updatedPodList.Items {
376+
podRequest := getCPURequest(pod.Spec)
377+
containerRestarts := getContainerRestarts(pod.Status)
378+
framework.Logf("podReq: %v, containerRestarts: %v", podRequest, getContainerRestarts(pod.Status))
379+
if podRequest.Cmp(ParseQuantityOrDie(updatedCPU)) == 0 {
380+
foundUpdated += 1
381+
}
382+
foundContainerRestarts += containerRestarts
383+
}
384+
gomega.Expect(foundUpdated).To(gomega.Equal(replicas))
385+
gomega.Expect(foundContainerRestarts).To(gomega.Equal(expectedContainerRestarts))
386+
})
387+
388+
// TODO(maxcao13): disruptive in-place fails and we have to evict (InPlaceOrRecreate)
389+
// This particular test checks if we fallback after a resize infeasible, particularly from the node not having enough resources (as specified in the AEP)
390+
// Should we check other conditions that result in eviction fallback?
391+
ginkgo.It("falls back to evicting pods when in-place update is Infeasible when update mode is InPlaceOrRecreate", func() {
392+
ginkgo.By("Setting up a hamster deployment")
393+
replicas := int32(2)
394+
SetupHamsterDeployment(f, "100m", "100Mi", replicas)
395+
podList, err := GetHamsterPods(f)
396+
gomega.Expect(err).NotTo(gomega.HaveOccurred())
397+
398+
ginkgo.By("Setting up a VPA CRD")
399+
containerName := GetHamsterContainerNameByIndex(0)
400+
updatedCPU := "999" // infeasible target
401+
vpaCRD := test.VerticalPodAutoscaler().
402+
WithName("hamster-vpa").
403+
WithNamespace(f.Namespace.Name).
404+
WithTargetRef(hamsterTargetRef).
405+
WithContainer(containerName).
406+
WithUpdateMode(vpa_types.UpdateModeInPlaceOrRecreate).
407+
AppendRecommendation(
408+
test.Recommendation().
409+
WithContainer(containerName).
410+
WithTarget(updatedCPU, "200Mi").
411+
WithLowerBound("150m", "150Mi").
412+
WithUpperBound("300m", "250Mi").
413+
GetContainerResources()).
414+
Get()
415+
416+
InstallVPA(f, vpaCRD)
417+
418+
ginkgo.By(fmt.Sprintf("Waiting for in-place update, hoping it won't happen, sleep for %s", VpaEvictionTimeout.String()))
419+
CheckNoContainersRestarted(f)
420+
421+
ginkgo.By("Waiting for pods to be evicted")
422+
err = WaitForPodsEvicted(f, podList)
423+
gomega.Expect(err).NotTo(gomega.HaveOccurred())
424+
})
425+
426+
ginkgo.It("falls back to evicting pods when pod QoS class changes when update mode is InPlaceOrRecreate", func() {
427+
ginkgo.By("Setting up a hamster deployment")
428+
replicas := int32(2)
429+
430+
d := NewHamsterDeploymentWithGuaranteedResources(f, ParseQuantityOrDie("300m"), ParseQuantityOrDie("400Mi"))
431+
d.Spec.Replicas = &replicas
432+
d, err := f.ClientSet.AppsV1().Deployments(f.Namespace.Name).Create(context.TODO(), d, metav1.CreateOptions{})
433+
gomega.Expect(err).NotTo(gomega.HaveOccurred(), "unexpected error when starting deployment creation")
434+
err = framework_deployment.WaitForDeploymentComplete(f.ClientSet, d)
435+
gomega.Expect(err).NotTo(gomega.HaveOccurred(), "unexpected error waiting for deployment creation to finish")
436+
437+
podList, err := GetHamsterPods(f)
438+
gomega.Expect(err).NotTo(gomega.HaveOccurred())
439+
440+
ginkgo.By("Setting up a VPA CRD")
441+
containerName := GetHamsterContainerNameByIndex(0)
442+
updatedCPU := "200m"
443+
vpaCRD := test.VerticalPodAutoscaler().
444+
WithName("hamster-vpa").
445+
WithNamespace(f.Namespace.Name).
446+
WithTargetRef(hamsterTargetRef).
447+
WithContainer(containerName).
448+
WithUpdateMode(vpa_types.UpdateModeInPlaceOrRecreate).
449+
AppendRecommendation(
450+
test.Recommendation().
451+
WithContainer(containerName).
452+
WithTarget(updatedCPU, "200Mi").
453+
WithLowerBound("150m", "150Mi").
454+
WithUpperBound("300m", "250Mi").
455+
GetContainerResources()).
456+
Get()
457+
458+
InstallVPA(f, vpaCRD)
459+
460+
ginkgo.By(fmt.Sprintf("Waiting for in-place update, hoping it won't happen, sleep for %s", VpaEvictionTimeout.String()))
461+
CheckNoContainersRestarted(f)
462+
463+
ginkgo.By("Waiting for pods to be evicted")
464+
err = WaitForPodsEvicted(f, podList)
465+
gomega.Expect(err).NotTo(gomega.HaveOccurred())
466+
})
467+
468+
ginkgo.It("falls back to evicting pods when resize is Deferred and more than 1 minute has elapsed since last in-place update when update mode is InPlaceOrRecreate", func() {
469+
ginkgo.By("Setting up a hamster deployment")
470+
replicas := int32(2)
471+
SetupHamsterDeployment(f, "100m", "100Mi", replicas)
472+
podList, err := GetHamsterPods(f)
473+
gomega.Expect(err).NotTo(gomega.HaveOccurred())
474+
475+
ginkgo.By("Setting up a VPA CRD")
476+
containerName := GetHamsterContainerNameByIndex(0)
477+
478+
// get node name
479+
nodeName := podList.Items[0].Spec.NodeName
480+
node, err := f.ClientSet.CoreV1().Nodes().Get(context.TODO(), nodeName, metav1.GetOptions{})
481+
gomega.Expect(err).NotTo(gomega.HaveOccurred())
482+
allocatableCPU := node.Status.Allocatable[apiv1.ResourceCPU]
483+
updatedCPU := allocatableCPU.String()
484+
485+
vpaCRD := test.VerticalPodAutoscaler().
486+
WithName("hamster-vpa").
487+
WithNamespace(f.Namespace.Name).
488+
WithTargetRef(hamsterTargetRef).
489+
WithContainer(containerName).
490+
WithUpdateMode(vpa_types.UpdateModeInPlaceOrRecreate).
491+
AppendRecommendation(
492+
test.Recommendation().
493+
WithContainer(containerName).
494+
WithTarget(updatedCPU, "200Mi").
495+
WithLowerBound("150m", "150Mi").
496+
WithUpperBound("300m", "250Mi").
497+
GetContainerResources()).
498+
Get()
499+
500+
InstallVPA(f, vpaCRD)
501+
502+
ginkgo.By(fmt.Sprintf("Waiting for in-place update, hoping it won't happen, sleep for %s", VpaEvictionTimeout.String()))
503+
CheckNoContainersRestarted(f)
504+
505+
ginkgo.By("Waiting for pods to be evicted")
506+
err = WaitForPodsEvicted(f, podList)
507+
gomega.Expect(err).NotTo(gomega.HaveOccurred())
508+
})
509+
170510
perControllerTests := []struct {
171511
apiVersion string
172512
kind string
@@ -519,6 +859,14 @@ func getCPURequest(podSpec apiv1.PodSpec) resource.Quantity {
519859
return podSpec.Containers[0].Resources.Requests[apiv1.ResourceCPU]
520860
}
521861

862+
// getContainerRestarts returns the sum of container restartCounts for all containers in the pod
863+
func getContainerRestarts(podStatus apiv1.PodStatus) (restarts int32) {
864+
for _, containerStatus := range podStatus.ContainerStatuses {
865+
restarts += containerStatus.RestartCount
866+
}
867+
return
868+
}
869+
522870
func killPod(f *framework.Framework, podList *apiv1.PodList) {
523871
f.ClientSet.CoreV1().Pods(f.Namespace.Name).Delete(context.TODO(), podList.Items[0].Name, metav1.DeleteOptions{})
524872
err := WaitForPodsRestarted(f, podList)

0 commit comments

Comments
 (0)