@@ -35,6 +35,7 @@ import (
35
35
"k8s.io/apimachinery/pkg/util/wait"
36
36
"k8s.io/autoscaler/vertical-pod-autoscaler/e2e/utils"
37
37
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"
38
39
"k8s.io/autoscaler/vertical-pod-autoscaler/pkg/utils/annotations"
39
40
clientset "k8s.io/client-go/kubernetes"
40
41
"k8s.io/kubernetes/test/e2e/framework"
@@ -50,6 +51,236 @@ import (
50
51
"github.com/onsi/gomega"
51
52
)
52
53
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
+ // 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
+
53
284
var _ = ActuationSuiteE2eDescribe ("Actuation" , func () {
54
285
f := framework .NewDefaultFramework ("vertical-pod-autoscaling" )
55
286
f .NamespacePodSecurityEnforceLevel = podsecurity .LevelBaseline
@@ -519,6 +750,10 @@ func getCPURequest(podSpec apiv1.PodSpec) resource.Quantity {
519
750
return podSpec .Containers [0 ].Resources .Requests [apiv1 .ResourceCPU ]
520
751
}
521
752
753
+ func getCPURequestFromStatus (podStatus apiv1.PodStatus ) resource.Quantity {
754
+ return podStatus .ContainerStatuses [0 ].Resources .Requests [apiv1 .ResourceCPU ]
755
+ }
756
+
522
757
func killPod (f * framework.Framework , podList * apiv1.PodList ) {
523
758
f .ClientSet .CoreV1 ().Pods (f .Namespace .Name ).Delete (context .TODO (), podList .Items [0 ].Name , metav1.DeleteOptions {})
524
759
err := WaitForPodsRestarted (f , podList )
0 commit comments