@@ -35,6 +35,7 @@ import (
3535 "k8s.io/apimachinery/pkg/util/wait"
3636 "k8s.io/autoscaler/vertical-pod-autoscaler/e2e/utils"
3737 vpa_types "k8s.io/autoscaler/vertical-pod-autoscaler/pkg/apis/autoscaling.k8s.io/v1"
38+ restriction "k8s.io/autoscaler/vertical-pod-autoscaler/pkg/updater/restriction"
3839 "k8s.io/autoscaler/vertical-pod-autoscaler/pkg/utils/annotations"
3940 clientset "k8s.io/client-go/kubernetes"
4041 "k8s.io/kubernetes/test/e2e/framework"
@@ -50,6 +51,237 @@ import (
5051 "github.com/onsi/gomega"
5152)
5253
54+ var _ = ActuationSuiteE2eDescribe ("Actuation [InPlaceOrRecreate]" , func () {
55+ f := framework .NewDefaultFramework ("vertical-pod-autoscaling" )
56+ f .NamespacePodSecurityEnforceLevel = podsecurity .LevelBaseline
57+
58+ ginkgo .BeforeEach (func () {
59+ checkInPlaceOrRecreateTestsEnabled (f , true , true )
60+ })
61+
62+ ginkgo .It ("still applies recommendations on restart when update mode is InPlaceOrRecreate" , func () {
63+ ginkgo .By ("Setting up a hamster deployment" )
64+ SetupHamsterDeployment (f , "100m" , "100Mi" , defaultHamsterReplicas )
65+ podList , err := GetHamsterPods (f )
66+ gomega .Expect (err ).NotTo (gomega .HaveOccurred ())
67+ podSet := MakePodSet (podList )
68+
69+ ginkgo .By ("Setting up a VPA CRD in mode InPlaceOrRecreate" )
70+ containerName := GetHamsterContainerNameByIndex (0 )
71+ vpaCRD := test .VerticalPodAutoscaler ().
72+ WithName ("hamster-vpa" ).
73+ WithNamespace (f .Namespace .Name ).
74+ WithTargetRef (hamsterTargetRef ).
75+ WithUpdateMode (vpa_types .UpdateModeInPlaceOrRecreate ).
76+ WithContainer (containerName ).
77+ AppendRecommendation (
78+ test .Recommendation ().
79+ WithContainer (containerName ).
80+ WithTarget ("200m" , "" ).
81+ WithLowerBound ("200m" , "" ).
82+ WithUpperBound ("200m" , "" ).
83+ GetContainerResources ()).
84+ Get ()
85+
86+ InstallVPA (f , vpaCRD )
87+ updatedCPURequest := ParseQuantityOrDie ("200m" )
88+
89+ ginkgo .By (fmt .Sprintf ("Waiting for pods to be evicted, hoping it won't happen, sleep for %s" , VpaEvictionTimeout .String ()))
90+ CheckNoPodsEvicted (f , podSet )
91+ ginkgo .By ("Forcefully killing one pod" )
92+ killPod (f , podList )
93+
94+ ginkgo .By ("Checking that request was modified after forceful restart" )
95+ updatedPodList , _ := GetHamsterPods (f )
96+ var foundUpdated int32
97+ for _ , pod := range updatedPodList .Items {
98+ podRequest := getCPURequest (pod .Spec )
99+ framework .Logf ("podReq: %v" , podRequest )
100+ if podRequest .Cmp (updatedCPURequest ) == 0 {
101+ foundUpdated += 1
102+ }
103+ }
104+ gomega .Expect (foundUpdated ).To (gomega .Equal (defaultHamsterReplicas ))
105+ })
106+
107+ ginkgo .It ("applies in-place updates to all containers when update mode is InPlaceOrRecreate" , func () {
108+ ginkgo .By ("Setting up a hamster deployment" )
109+ d := NewNHamstersDeployment (f , 2 /*number of containers*/ )
110+ d .Spec .Template .Spec .Containers [0 ].Resources .Requests = apiv1.ResourceList {
111+ apiv1 .ResourceCPU : ParseQuantityOrDie ("100m" ),
112+ apiv1 .ResourceMemory : ParseQuantityOrDie ("100Mi" ),
113+ }
114+ d .Spec .Template .Spec .Containers [1 ].Resources .Requests = apiv1.ResourceList {
115+ apiv1 .ResourceCPU : ParseQuantityOrDie ("100m" ),
116+ apiv1 .ResourceMemory : ParseQuantityOrDie ("100Mi" ),
117+ }
118+ targetCPU := "200m"
119+ _ = startDeploymentPods (f , d ) // 3 replicas
120+ container1Name := GetHamsterContainerNameByIndex (0 )
121+ container2Name := GetHamsterContainerNameByIndex (1 )
122+ podList , err := GetHamsterPods (f )
123+ gomega .Expect (err ).NotTo (gomega .HaveOccurred ())
124+
125+ ginkgo .By ("Setting up a VPA CRD" )
126+ vpaCRD := test .VerticalPodAutoscaler ().
127+ WithName ("hamster-vpa" ).
128+ WithNamespace (f .Namespace .Name ).
129+ WithTargetRef (hamsterTargetRef ).
130+ WithContainer (container1Name ).
131+ WithContainer (container2Name ).
132+ WithUpdateMode (vpa_types .UpdateModeInPlaceOrRecreate ).
133+ AppendRecommendation (
134+ test .Recommendation ().
135+ WithContainer (container1Name ).
136+ WithTarget (targetCPU , "" ).
137+ WithLowerBound ("200m" , "" ).
138+ WithUpperBound ("200m" , "" ).
139+ GetContainerResources ()).
140+ AppendRecommendation (
141+ test .Recommendation ().
142+ WithContainer (container2Name ).
143+ WithTarget (targetCPU , "" ).
144+ WithLowerBound ("200m" , "" ).
145+ WithUpperBound ("200m" , "" ).
146+ GetContainerResources ()).
147+ Get ()
148+
149+ InstallVPA (f , vpaCRD )
150+
151+ ginkgo .By ("Checking that resources were modified due to in-place update, not due to evictions" )
152+ err = WaitForPodsUpdatedWithoutEviction (f , podList )
153+ gomega .Expect (err ).NotTo (gomega .HaveOccurred ())
154+
155+ ginkgo .By ("Checking that container resources were actually updated" )
156+ gomega .Eventually (func () error {
157+ updatedPodList , err := GetHamsterPods (f )
158+ if err != nil {
159+ return err
160+ }
161+ for _ , pod := range updatedPodList .Items {
162+ for _ , container := range pod .Spec .Containers {
163+ if container .Name == container1Name {
164+ request := container .Resources .Requests [apiv1 .ResourceCPU ]
165+ if request .Cmp (ParseQuantityOrDie (targetCPU )) != 0 {
166+ framework .Logf ("%v/%v has not been updated to %v yet: currently=%v" , pod .Name , container .Name , targetCPU , request .String ())
167+ return fmt .Errorf ("%s CPU request not updated" , container1Name )
168+ }
169+ }
170+ if container .Name == container2Name {
171+ request := container .Resources .Requests [apiv1 .ResourceCPU ]
172+ if request .Cmp (ParseQuantityOrDie (targetCPU )) != 0 {
173+ framework .Logf ("%v/%v has not been updated to %v yet: currently=%v" , pod .Name , container .Name , targetCPU , request .String ())
174+ return fmt .Errorf ("%s CPU request not updated" , container2Name )
175+ }
176+ }
177+ }
178+ }
179+ return nil
180+ }, VpaInPlaceTimeout * 3 , 15 * time .Second ).Should (gomega .Succeed ())
181+ })
182+
183+ ginkgo .It ("falls back to evicting pods when in-place update is Infeasible when update mode is InPlaceOrRecreate" , func () {
184+ ginkgo .By ("Setting up a hamster deployment" )
185+ replicas := int32 (2 )
186+ SetupHamsterDeployment (f , "100m" , "100Mi" , replicas )
187+ podList , err := GetHamsterPods (f )
188+ gomega .Expect (err ).NotTo (gomega .HaveOccurred ())
189+
190+ ginkgo .By ("Setting up a VPA CRD" )
191+ containerName := GetHamsterContainerNameByIndex (0 )
192+ updatedCPU := "999" // infeasible target
193+ vpaCRD := test .VerticalPodAutoscaler ().
194+ WithName ("hamster-vpa" ).
195+ WithNamespace (f .Namespace .Name ).
196+ WithTargetRef (hamsterTargetRef ).
197+ WithContainer (containerName ).
198+ WithUpdateMode (vpa_types .UpdateModeInPlaceOrRecreate ).
199+ AppendRecommendation (
200+ test .Recommendation ().
201+ WithContainer (containerName ).
202+ WithTarget (updatedCPU , "" ).
203+ WithLowerBound ("200m" , "" ).
204+ WithUpperBound ("200m" , "" ).
205+ GetContainerResources ()).
206+ Get ()
207+
208+ InstallVPA (f , vpaCRD )
209+
210+ ginkgo .By ("Waiting for pods to be evicted" )
211+ err = WaitForPodsEvicted (f , podList )
212+ gomega .Expect (err ).NotTo (gomega .HaveOccurred ())
213+ })
214+
215+ ginkgo .It ("falls back to evicting pods when resize is Deferred and more than 5 minute has elapsed since last in-place update when update mode is InPlaceOrRecreate" , func () {
216+ ginkgo .By ("Setting up a hamster deployment" )
217+ replicas := int32 (2 )
218+ SetupHamsterDeployment (f , "100m" , "100Mi" , replicas )
219+ podList , err := GetHamsterPods (f )
220+ gomega .Expect (err ).NotTo (gomega .HaveOccurred ())
221+
222+ ginkgo .By ("Setting up a VPA CRD" )
223+ containerName := GetHamsterContainerNameByIndex (0 )
224+
225+ // get node name
226+ nodeName := podList .Items [0 ].Spec .NodeName
227+ node , err := f .ClientSet .CoreV1 ().Nodes ().Get (context .TODO (), nodeName , metav1.GetOptions {})
228+ gomega .Expect (err ).NotTo (gomega .HaveOccurred ())
229+ allocatableCPU := node .Status .Allocatable [apiv1 .ResourceCPU ]
230+ updatedCPU := allocatableCPU .String ()
231+
232+ vpaCRD := test .VerticalPodAutoscaler ().
233+ WithName ("hamster-vpa" ).
234+ WithNamespace (f .Namespace .Name ).
235+ WithTargetRef (hamsterTargetRef ).
236+ WithContainer (containerName ).
237+ WithUpdateMode (vpa_types .UpdateModeInPlaceOrRecreate ).
238+ AppendRecommendation (
239+ test .Recommendation ().
240+ WithContainer (containerName ).
241+ WithTarget (updatedCPU , "" ).
242+ WithLowerBound ("200m" , "" ).
243+ WithUpperBound ("200m" , "" ).
244+ GetContainerResources ()).
245+ Get ()
246+
247+ InstallVPA (f , vpaCRD )
248+
249+ ginkgo .By ("Waiting for status to be Deferred" )
250+ gomega .Eventually (func () error {
251+ updatedPodList , err := GetHamsterPods (f )
252+ if err != nil {
253+ return err
254+ }
255+ for _ , pod := range updatedPodList .Items {
256+ if pod .Status .Resize == apiv1 .PodResizeStatusDeferred {
257+ return nil
258+ }
259+ }
260+ return fmt .Errorf ("status not deferred" )
261+ }, VpaInPlaceTimeout , 5 * time .Second ).Should (gomega .Succeed ())
262+
263+ ginkgo .By ("Making sure pods are not evicted yet" )
264+ gomega .Consistently (func () error {
265+ updatedPodList , err := GetHamsterPods (f )
266+ if err != nil {
267+ return fmt .Errorf ("failed to get pods: %v" , err )
268+ }
269+ for _ , pod := range updatedPodList .Items {
270+ request := getCPURequestFromStatus (pod .Status )
271+ if request .Cmp (ParseQuantityOrDie (updatedCPU )) == 0 {
272+ framework .Logf ("%v/%v updated to %v, that wasn't supposed to happen this early" , pod .Name , containerName , updatedCPU )
273+ return fmt .Errorf ("%s CPU request should not have been updated" , containerName )
274+ }
275+ }
276+ return nil
277+ }, restriction .DeferredResizeUpdateTimeout , 10 * time .Second ).Should (gomega .Succeed ())
278+
279+ ginkgo .By ("Waiting for pods to be evicted" )
280+ err = WaitForPodsEvicted (f , podList )
281+ gomega .Expect (err ).NotTo (gomega .HaveOccurred ())
282+ })
283+ })
284+
53285var _ = ActuationSuiteE2eDescribe ("Actuation" , func () {
54286 f := framework .NewDefaultFramework ("vertical-pod-autoscaling" )
55287 f .NamespacePodSecurityEnforceLevel = podsecurity .LevelBaseline
@@ -519,6 +751,10 @@ func getCPURequest(podSpec apiv1.PodSpec) resource.Quantity {
519751 return podSpec .Containers [0 ].Resources .Requests [apiv1 .ResourceCPU ]
520752}
521753
754+ func getCPURequestFromStatus (podStatus apiv1.PodStatus ) resource.Quantity {
755+ return podStatus .ContainerStatuses [0 ].Resources .Requests [apiv1 .ResourceCPU ]
756+ }
757+
522758func killPod (f * framework.Framework , podList * apiv1.PodList ) {
523759 f .ClientSet .CoreV1 ().Pods (f .Namespace .Name ).Delete (context .TODO (), podList .Items [0 ].Name , metav1.DeleteOptions {})
524760 err := WaitForPodsRestarted (f , podList )
0 commit comments