@@ -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+
522870func 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