@@ -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,236 @@ import (
5051 "github.com/onsi/gomega"
5152)
5253
54+ var _ = ActuationSuiteE2eDescribe ("Actuation" , ginkgo .Label ("FG: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+ // TODO: add e2e test to verify metrics are getting updated
108+ ginkgo .It ("applies in-place updates to all containers when update mode is InPlaceOrRecreate" , func () {
109+ ginkgo .By ("Setting up a hamster deployment" )
110+ d := NewNHamstersDeployment (f , 2 /*number of containers*/ )
111+ d .Spec .Template .Spec .Containers [0 ].Resources .Requests = apiv1.ResourceList {
112+ apiv1 .ResourceCPU : ParseQuantityOrDie ("100m" ),
113+ apiv1 .ResourceMemory : ParseQuantityOrDie ("100Mi" ),
114+ }
115+ d .Spec .Template .Spec .Containers [1 ].Resources .Requests = apiv1.ResourceList {
116+ apiv1 .ResourceCPU : ParseQuantityOrDie ("100m" ),
117+ apiv1 .ResourceMemory : ParseQuantityOrDie ("100Mi" ),
118+ }
119+ targetCPU := "200m"
120+ targetMemory := "200Mi"
121+ _ = startDeploymentPods (f , d ) // 3 replicas
122+ container1Name := GetHamsterContainerNameByIndex (0 )
123+ container2Name := GetHamsterContainerNameByIndex (1 )
124+ podList , err := GetHamsterPods (f )
125+ gomega .Expect (err ).NotTo (gomega .HaveOccurred ())
126+
127+ ginkgo .By ("Setting up a VPA CRD" )
128+ vpaCRD := test .VerticalPodAutoscaler ().
129+ WithName ("hamster-vpa" ).
130+ WithNamespace (f .Namespace .Name ).
131+ WithTargetRef (hamsterTargetRef ).
132+ WithContainer (container1Name ).
133+ WithContainer (container2Name ).
134+ WithUpdateMode (vpa_types .UpdateModeInPlaceOrRecreate ).
135+ AppendRecommendation (
136+ test .Recommendation ().
137+ WithContainer (container1Name ).
138+ WithTarget (targetCPU , targetMemory ).
139+ WithLowerBound (targetCPU , targetMemory ).
140+ WithUpperBound (targetCPU , targetMemory ).
141+ GetContainerResources ()).
142+ AppendRecommendation (
143+ test .Recommendation ().
144+ WithContainer (container2Name ).
145+ WithTarget (targetCPU , targetMemory ).
146+ WithLowerBound (targetCPU , targetMemory ).
147+ WithUpperBound (targetCPU , targetMemory ).
148+ GetContainerResources ()).
149+ Get ()
150+
151+ InstallVPA (f , vpaCRD )
152+
153+ ginkgo .By ("Checking that resources were modified due to in-place update, not due to evictions" )
154+ err = WaitForPodsUpdatedWithoutEviction (f , podList )
155+ gomega .Expect (err ).NotTo (gomega .HaveOccurred ())
156+
157+ ginkgo .By ("Checking that container resources were actually updated" )
158+ gomega .Eventually (func () error {
159+ updatedPodList , err := GetHamsterPods (f )
160+ if err != nil {
161+ return err
162+ }
163+ for _ , pod := range updatedPodList .Items {
164+ for _ , container := range pod .Status .ContainerStatuses {
165+ cpuRequest := container .Resources .Requests [apiv1 .ResourceCPU ]
166+ memoryRequest := container .Resources .Requests [apiv1 .ResourceMemory ]
167+ if cpuRequest .Cmp (ParseQuantityOrDie (targetCPU )) != 0 {
168+ framework .Logf ("%v/%v has not been updated to %v yet: currently=%v" , pod .Name , container .Name , targetCPU , cpuRequest .String ())
169+ return fmt .Errorf ("%s CPU request not updated" , container .Name )
170+ }
171+ if memoryRequest .Cmp (ParseQuantityOrDie (targetMemory )) != 0 {
172+ framework .Logf ("%v/%v has not been updated to %v yet: currently=%v" , pod .Name , container .Name , targetMemory , memoryRequest .String ())
173+ return fmt .Errorf ("%s Memory request not updated" , container .Name )
174+ }
175+ }
176+ }
177+ return nil
178+ }, VpaInPlaceTimeout * 3 , 15 * time .Second ).Should (gomega .Succeed ())
179+ })
180+
181+ ginkgo .It ("falls back to evicting pods when in-place update is Infeasible when update mode is InPlaceOrRecreate" , func () {
182+ ginkgo .By ("Setting up a hamster deployment" )
183+ replicas := int32 (2 )
184+ SetupHamsterDeployment (f , "100m" , "100Mi" , replicas )
185+ podList , err := GetHamsterPods (f )
186+ gomega .Expect (err ).NotTo (gomega .HaveOccurred ())
187+
188+ ginkgo .By ("Setting up a VPA CRD" )
189+ containerName := GetHamsterContainerNameByIndex (0 )
190+ updatedCPU := "999" // infeasible target
191+ vpaCRD := test .VerticalPodAutoscaler ().
192+ WithName ("hamster-vpa" ).
193+ WithNamespace (f .Namespace .Name ).
194+ WithTargetRef (hamsterTargetRef ).
195+ WithContainer (containerName ).
196+ WithUpdateMode (vpa_types .UpdateModeInPlaceOrRecreate ).
197+ AppendRecommendation (
198+ test .Recommendation ().
199+ WithContainer (containerName ).
200+ WithTarget (updatedCPU , "" ).
201+ WithLowerBound ("200m" , "" ).
202+ WithUpperBound ("200m" , "" ).
203+ GetContainerResources ()).
204+ Get ()
205+
206+ InstallVPA (f , vpaCRD )
207+
208+ ginkgo .By ("Waiting for pods to be evicted" )
209+ err = WaitForPodsEvicted (f , podList )
210+ gomega .Expect (err ).NotTo (gomega .HaveOccurred ())
211+ })
212+
213+ 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 () {
214+ ginkgo .By ("Setting up a hamster deployment" )
215+ replicas := int32 (2 )
216+ SetupHamsterDeployment (f , "100m" , "100Mi" , replicas )
217+ podList , err := GetHamsterPods (f )
218+ gomega .Expect (err ).NotTo (gomega .HaveOccurred ())
219+
220+ ginkgo .By ("Setting up a VPA CRD" )
221+ containerName := GetHamsterContainerNameByIndex (0 )
222+
223+ // we can force deferred resize by setting the target CPU to the allocatable CPU of the node
224+ // it will be close enough to the node capacity, such that the kubelet defers instead of marking it infeasible
225+ nodeName := podList .Items [0 ].Spec .NodeName
226+ node , err := f .ClientSet .CoreV1 ().Nodes ().Get (context .TODO (), nodeName , metav1.GetOptions {})
227+ gomega .Expect (err ).NotTo (gomega .HaveOccurred ())
228+ allocatableCPU := node .Status .Allocatable [apiv1 .ResourceCPU ]
229+ updatedCPU := allocatableCPU .String ()
230+
231+ vpaCRD := test .VerticalPodAutoscaler ().
232+ WithName ("hamster-vpa" ).
233+ WithNamespace (f .Namespace .Name ).
234+ WithTargetRef (hamsterTargetRef ).
235+ WithContainer (containerName ).
236+ WithUpdateMode (vpa_types .UpdateModeInPlaceOrRecreate ).
237+ AppendRecommendation (
238+ test .Recommendation ().
239+ WithContainer (containerName ).
240+ WithTarget (updatedCPU , "" ).
241+ WithLowerBound ("200m" , "" ).
242+ WithUpperBound ("200m" , "" ).
243+ GetContainerResources ()).
244+ Get ()
245+
246+ InstallVPA (f , vpaCRD )
247+
248+ ginkgo .By ("Waiting for status to be Deferred" )
249+ gomega .Eventually (func () error {
250+ updatedPodList , err := GetHamsterPods (f )
251+ if err != nil {
252+ return err
253+ }
254+ for _ , pod := range updatedPodList .Items {
255+ if pod .Status .Resize == apiv1 .PodResizeStatusDeferred {
256+ return nil
257+ }
258+ }
259+ return fmt .Errorf ("status not deferred" )
260+ }, VpaInPlaceTimeout , 5 * time .Second ).Should (gomega .Succeed ())
261+
262+ ginkgo .By ("Making sure pods are not evicted yet" )
263+ gomega .Consistently (func () error {
264+ updatedPodList , err := GetHamsterPods (f )
265+ if err != nil {
266+ return fmt .Errorf ("failed to get pods: %v" , err )
267+ }
268+ for _ , pod := range updatedPodList .Items {
269+ request := getCPURequestFromStatus (pod .Status )
270+ if request .Cmp (ParseQuantityOrDie (updatedCPU )) == 0 {
271+ framework .Logf ("%v/%v updated to %v, that wasn't supposed to happen this early" , pod .Name , containerName , updatedCPU )
272+ return fmt .Errorf ("%s CPU request should not have been updated" , containerName )
273+ }
274+ }
275+ return nil
276+ }, restriction .DeferredResizeUpdateTimeout , 10 * time .Second ).Should (gomega .Succeed ())
277+
278+ ginkgo .By ("Waiting for pods to be evicted" )
279+ err = WaitForPodsEvicted (f , podList )
280+ gomega .Expect (err ).NotTo (gomega .HaveOccurred ())
281+ })
282+ })
283+
53284var _ = ActuationSuiteE2eDescribe ("Actuation" , func () {
54285 f := framework .NewDefaultFramework ("vertical-pod-autoscaling" )
55286 f .NamespacePodSecurityEnforceLevel = podsecurity .LevelBaseline
@@ -519,6 +750,10 @@ func getCPURequest(podSpec apiv1.PodSpec) resource.Quantity {
519750 return podSpec .Containers [0 ].Resources .Requests [apiv1 .ResourceCPU ]
520751}
521752
753+ func getCPURequestFromStatus (podStatus apiv1.PodStatus ) resource.Quantity {
754+ return podStatus .ContainerStatuses [0 ].Resources .Requests [apiv1 .ResourceCPU ]
755+ }
756+
522757func killPod (f * framework.Framework , podList * apiv1.PodList ) {
523758 f .ClientSet .CoreV1 ().Pods (f .Namespace .Name ).Delete (context .TODO (), podList .Items [0 ].Name , metav1.DeleteOptions {})
524759 err := WaitForPodsRestarted (f , podList )
0 commit comments