1
16
17 package evictions
18
19 import (
20 "context"
21 "encoding/json"
22 "fmt"
23 "reflect"
24 "strings"
25 "sync"
26 "sync/atomic"
27 "testing"
28 "time"
29
30 v1 "k8s.io/api/core/v1"
31 policyv1 "k8s.io/api/policy/v1"
32 policyv1beta1 "k8s.io/api/policy/v1beta1"
33 apierrors "k8s.io/apimachinery/pkg/api/errors"
34 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
35 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
36 "k8s.io/apimachinery/pkg/runtime"
37 "k8s.io/apimachinery/pkg/runtime/schema"
38 "k8s.io/apimachinery/pkg/types"
39 utilerrors "k8s.io/apimachinery/pkg/util/errors"
40 "k8s.io/apimachinery/pkg/util/intstr"
41 "k8s.io/apimachinery/pkg/util/uuid"
42 "k8s.io/apimachinery/pkg/util/wait"
43 "k8s.io/apiserver/pkg/util/feature"
44 cacheddiscovery "k8s.io/client-go/discovery/cached/memory"
45 "k8s.io/client-go/dynamic"
46 "k8s.io/client-go/informers"
47 clientset "k8s.io/client-go/kubernetes"
48 policyv1client "k8s.io/client-go/kubernetes/typed/policy/v1"
49 restclient "k8s.io/client-go/rest"
50 "k8s.io/client-go/restmapper"
51 "k8s.io/client-go/scale"
52 "k8s.io/client-go/tools/cache"
53 featuregatetesting "k8s.io/component-base/featuregate/testing"
54 "k8s.io/klog/v2"
55 kubeapiservertesting "k8s.io/kubernetes/cmd/kube-apiserver/app/testing"
56 podutil "k8s.io/kubernetes/pkg/api/v1/pod"
57 "k8s.io/kubernetes/pkg/controller/disruption"
58 "k8s.io/kubernetes/pkg/features"
59 "k8s.io/kubernetes/test/integration/framework"
60 "k8s.io/kubernetes/test/utils/ktesting"
61 )
62
63 const (
64 numOfEvictions = 10
65 )
66
67
68
69 func TestConcurrentEvictionRequests(t *testing.T) {
70 podNameFormat := "test-pod-%d"
71
72 tCtx := ktesting.Init(t)
73 closeFn, rm, informers, _, clientSet := rmSetup(tCtx, t)
74 defer closeFn()
75
76 ns := framework.CreateNamespaceOrDie(clientSet, "concurrent-eviction-requests", t)
77 defer framework.DeleteNamespaceOrDie(clientSet, ns, t)
78 defer tCtx.Cancel("test has completed")
79
80 informers.Start(tCtx.Done())
81 go rm.Run(tCtx)
82
83 var gracePeriodSeconds int64 = 30
84 deleteOption := metav1.DeleteOptions{
85 GracePeriodSeconds: &gracePeriodSeconds,
86 }
87
88
89 for i := 0; i < numOfEvictions; i++ {
90 podName := fmt.Sprintf(podNameFormat, i)
91 pod := newPod(podName)
92
93 if _, err := clientSet.CoreV1().Pods(ns.Name).Create(context.TODO(), pod, metav1.CreateOptions{}); err != nil {
94 t.Errorf("Failed to create pod: %v", err)
95 }
96 pod.Status.Phase = v1.PodRunning
97 addPodConditionReady(pod)
98 if _, err := clientSet.CoreV1().Pods(ns.Name).UpdateStatus(context.TODO(), pod, metav1.UpdateOptions{}); err != nil {
99 t.Fatal(err)
100 }
101 }
102
103 waitToObservePods(t, informers.Core().V1().Pods().Informer(), numOfEvictions, v1.PodRunning)
104
105 pdb := newPDB()
106 if _, err := clientSet.PolicyV1().PodDisruptionBudgets(ns.Name).Create(context.TODO(), pdb, metav1.CreateOptions{}); err != nil {
107 t.Errorf("Failed to create PodDisruptionBudget: %v", err)
108 }
109
110 waitPDBStable(t, clientSet, ns.Name, pdb.Name, numOfEvictions)
111
112 var numberPodsEvicted uint32
113 errCh := make(chan error, 3*numOfEvictions)
114 var wg sync.WaitGroup
115
116 for i := 0; i < numOfEvictions; i++ {
117 wg.Add(1)
118 go func(id int, errCh chan error) {
119 defer wg.Done()
120 podName := fmt.Sprintf(podNameFormat, id)
121 eviction := newV1Eviction(ns.Name, podName, deleteOption)
122
123 err := wait.PollImmediate(5*time.Second, 60*time.Second, func() (bool, error) {
124 e := clientSet.PolicyV1().Evictions(ns.Name).Evict(context.TODO(), eviction)
125 switch {
126 case apierrors.IsTooManyRequests(e):
127 return false, nil
128 case apierrors.IsConflict(e):
129 return false, fmt.Errorf("Unexpected Conflict (409) error caused by failing to handle concurrent PDB updates: %v", e)
130 case e == nil:
131 return true, nil
132 default:
133 return false, e
134 }
135 })
136
137 if err != nil {
138 errCh <- err
139
140 }
141
142 _, err = clientSet.CoreV1().Pods(ns.Name).Get(context.TODO(), podName, metav1.GetOptions{})
143 switch {
144 case apierrors.IsNotFound(err):
145 atomic.AddUint32(&numberPodsEvicted, 1)
146
147 return
148 case err == nil:
149
150 errCh <- fmt.Errorf("Pod %q is expected to be evicted", podName)
151 default:
152 errCh <- err
153 }
154
155
156 e := clientSet.CoreV1().Pods(ns.Name).Delete(context.TODO(), podName, deleteOption)
157 if e != nil {
158 errCh <- e
159 }
160
161 }(i, errCh)
162 }
163
164 wg.Wait()
165
166 close(errCh)
167 var errList []error
168 if err := clientSet.PolicyV1().PodDisruptionBudgets(ns.Name).Delete(context.TODO(), pdb.Name, deleteOption); err != nil {
169 errList = append(errList, fmt.Errorf("Failed to delete PodDisruptionBudget: %v", err))
170 }
171 for err := range errCh {
172 errList = append(errList, err)
173 }
174 if len(errList) > 0 {
175 t.Fatal(utilerrors.NewAggregate(errList))
176 }
177
178 if atomic.LoadUint32(&numberPodsEvicted) != numOfEvictions {
179 t.Fatalf("fewer number of successful evictions than expected : %d", numberPodsEvicted)
180 }
181 }
182
183
184 func TestTerminalPodEviction(t *testing.T) {
185 tCtx := ktesting.Init(t)
186 closeFn, rm, informers, _, clientSet := rmSetup(tCtx, t)
187 defer closeFn()
188
189 ns := framework.CreateNamespaceOrDie(clientSet, "terminalpod-eviction", t)
190 defer framework.DeleteNamespaceOrDie(clientSet, ns, t)
191 defer tCtx.Cancel("test has completed")
192
193 informers.Start(tCtx.Done())
194 go rm.Run(tCtx)
195
196 var gracePeriodSeconds int64 = 30
197 deleteOption := metav1.DeleteOptions{
198 GracePeriodSeconds: &gracePeriodSeconds,
199 }
200 pod := newPod("test-terminal-pod1")
201 if _, err := clientSet.CoreV1().Pods(ns.Name).Create(context.TODO(), pod, metav1.CreateOptions{}); err != nil {
202 t.Errorf("Failed to create pod: %v", err)
203 }
204
205 pod.Status.Phase = v1.PodSucceeded
206 addPodConditionReady(pod)
207 if _, err := clientSet.CoreV1().Pods(ns.Name).UpdateStatus(context.TODO(), pod, metav1.UpdateOptions{}); err != nil {
208 t.Fatal(err)
209 }
210
211 waitToObservePods(t, informers.Core().V1().Pods().Informer(), 1, v1.PodSucceeded)
212
213 pdb := newPDB()
214 if _, err := clientSet.PolicyV1().PodDisruptionBudgets(ns.Name).Create(context.TODO(), pdb, metav1.CreateOptions{}); err != nil {
215 t.Errorf("Failed to create PodDisruptionBudget: %v", err)
216 }
217
218 waitPDBStable(t, clientSet, ns.Name, pdb.Name, 1)
219
220 pdbList, err := clientSet.PolicyV1().PodDisruptionBudgets(ns.Name).List(context.TODO(), metav1.ListOptions{})
221 if err != nil {
222 t.Fatalf("Error while listing pod disruption budget")
223 }
224 oldPdb := pdbList.Items[0]
225 eviction := newV1Eviction(ns.Name, pod.Name, deleteOption)
226 err = wait.PollImmediate(5*time.Second, 60*time.Second, func() (bool, error) {
227 e := clientSet.PolicyV1().Evictions(ns.Name).Evict(context.TODO(), eviction)
228 switch {
229 case apierrors.IsTooManyRequests(e):
230 return false, nil
231 case apierrors.IsConflict(e):
232 return false, fmt.Errorf("Unexpected Conflict (409) error caused by failing to handle concurrent PDB updates: %v", e)
233 case e == nil:
234 return true, nil
235 default:
236 return false, e
237 }
238 })
239 if err != nil {
240 t.Fatalf("Eviction of pod failed %v", err)
241 }
242 pdbList, err = clientSet.PolicyV1().PodDisruptionBudgets(ns.Name).List(context.TODO(), metav1.ListOptions{})
243 if err != nil {
244 t.Fatalf("Error while listing pod disruption budget")
245 }
246 newPdb := pdbList.Items[0]
247
248 if !reflect.DeepEqual(newPdb.Status.ObservedGeneration, oldPdb.Status.ObservedGeneration) {
249 t.Fatalf("Expected the pdb generation to be of same value %v but got %v", newPdb.Status.ObservedGeneration, oldPdb.Status.ObservedGeneration)
250 }
251
252 if err := clientSet.PolicyV1().PodDisruptionBudgets(ns.Name).Delete(context.TODO(), pdb.Name, deleteOption); err != nil {
253 t.Fatalf("Failed to delete pod disruption budget")
254 }
255 }
256
257
258 func TestEvictionVersions(t *testing.T) {
259 tCtx := ktesting.Init(t)
260 closeFn, rm, informers, config, clientSet := rmSetup(tCtx, t)
261 defer closeFn()
262 defer tCtx.Cancel("test has completed")
263
264 informers.Start(tCtx.Done())
265 go rm.Run(tCtx)
266
267 ns := "default"
268 subresource := "eviction"
269 pod := newPod("test")
270 if _, err := clientSet.CoreV1().Pods(ns).Create(context.TODO(), pod, metav1.CreateOptions{}); err != nil {
271 t.Errorf("Failed to create pod: %v", err)
272 }
273
274 dynamicClient, err := dynamic.NewForConfig(config)
275 if err != nil {
276 t.Fatalf("Failed to create clientset: %v", err)
277 }
278
279 podClient := dynamicClient.Resource(schema.GroupVersionResource{Group: "", Version: "v1", Resource: "pods"}).Namespace(ns)
280
281
282 if _, err := podClient.Get(context.TODO(), pod.Name, metav1.GetOptions{}, subresource); !apierrors.IsMethodNotSupported(err) {
283 t.Fatalf("expected MethodNotSupported for GET, got %v", err)
284 }
285
286
287 for _, patchType := range []types.PatchType{types.JSONPatchType, types.MergePatchType, types.StrategicMergePatchType, types.ApplyPatchType} {
288 if _, err := podClient.Patch(context.TODO(), pod.Name, patchType, []byte{}, metav1.PatchOptions{}, subresource); !apierrors.IsMethodNotSupported(err) {
289 t.Fatalf("expected MethodNotSupported for GET, got %v", err)
290 }
291 }
292
293 allowedEvictions := []runtime.Object{
294
295 &policyv1beta1.Eviction{
296 TypeMeta: metav1.TypeMeta{},
297 ObjectMeta: metav1.ObjectMeta{Name: pod.Name},
298 DeleteOptions: &metav1.DeleteOptions{DryRun: []string{metav1.DryRunAll}},
299 },
300
301 &policyv1beta1.Eviction{
302 TypeMeta: metav1.TypeMeta{APIVersion: "policy/v1beta1", Kind: "Eviction"},
303 ObjectMeta: metav1.ObjectMeta{Name: pod.Name},
304 DeleteOptions: &metav1.DeleteOptions{DryRun: []string{metav1.DryRunAll}},
305 },
306
307 &policyv1.Eviction{
308 TypeMeta: metav1.TypeMeta{},
309 ObjectMeta: metav1.ObjectMeta{Name: pod.Name},
310 DeleteOptions: &metav1.DeleteOptions{DryRun: []string{metav1.DryRunAll}},
311 },
312
313 &policyv1.Eviction{
314 TypeMeta: metav1.TypeMeta{APIVersion: "policy/v1", Kind: "Eviction"},
315 ObjectMeta: metav1.ObjectMeta{Name: pod.Name},
316 DeleteOptions: &metav1.DeleteOptions{DryRun: []string{metav1.DryRunAll}},
317 },
318 }
319 v1Status := schema.GroupVersionKind{Version: "v1", Kind: "Status"}
320 for _, allowedEviction := range allowedEvictions {
321 data, _ := json.Marshal(allowedEviction)
322 u := &unstructured.Unstructured{}
323 json.Unmarshal(data, u)
324 result, err := podClient.Create(context.TODO(), u, metav1.CreateOptions{}, subresource)
325 if err != nil {
326 t.Fatalf("error posting %s: %v", string(data), err)
327 }
328 if result.GroupVersionKind() != v1Status {
329 t.Fatalf("expected v1 Status, got %#v", result)
330 }
331 }
332
333
334 u := &unstructured.Unstructured{Object: map[string]interface{}{
335 "metadata": map[string]interface{}{"name": pod.Name},
336 "apiVersion": "policy/v2",
337 "kind": "Eviction",
338 }}
339 if _, err := podClient.Create(context.TODO(), u, metav1.CreateOptions{}, subresource); err == nil {
340 t.Fatal("expected error posting unknown Eviction version, got none")
341 } else if !strings.Contains(err.Error(), "policy/v2") {
342 t.Fatalf("expected error about policy/v2, got %#v", err)
343 }
344 }
345
346
347 func TestEvictionWithFinalizers(t *testing.T) {
348 cases := map[string]struct {
349 enablePodDisruptionConditions bool
350 phase v1.PodPhase
351 dryRun bool
352 wantDisruptionTargetCond bool
353 }{
354 "terminal pod with PodDisruptionConditions enabled": {
355 enablePodDisruptionConditions: true,
356 phase: v1.PodSucceeded,
357 wantDisruptionTargetCond: true,
358 },
359 "terminal pod with PodDisruptionConditions disabled": {
360 enablePodDisruptionConditions: false,
361 phase: v1.PodSucceeded,
362 wantDisruptionTargetCond: false,
363 },
364 "running pod with PodDisruptionConditions enabled": {
365 enablePodDisruptionConditions: true,
366 phase: v1.PodRunning,
367 wantDisruptionTargetCond: true,
368 },
369 "running pod with PodDisruptionConditions disabled": {
370 enablePodDisruptionConditions: false,
371 phase: v1.PodRunning,
372 wantDisruptionTargetCond: false,
373 },
374 "running pod with PodDisruptionConditions enabled should not update conditions in dry-run mode": {
375 enablePodDisruptionConditions: true,
376 phase: v1.PodRunning,
377 dryRun: true,
378 wantDisruptionTargetCond: false,
379 },
380 }
381 for name, tc := range cases {
382 t.Run(name, func(t *testing.T) {
383 tCtx := ktesting.Init(t)
384 closeFn, rm, informers, _, clientSet := rmSetup(tCtx, t)
385 defer closeFn()
386
387 ns := framework.CreateNamespaceOrDie(clientSet, "eviction-with-finalizers", t)
388 defer framework.DeleteNamespaceOrDie(clientSet, ns, t)
389 defer featuregatetesting.SetFeatureGateDuringTest(t, feature.DefaultFeatureGate, features.PodDisruptionConditions, tc.enablePodDisruptionConditions)()
390 defer tCtx.Cancel("test has completed")
391
392 informers.Start(tCtx.Done())
393 go rm.Run(tCtx)
394
395 pod := newPod("pod")
396 pod.ObjectMeta.Finalizers = []string{"test.k8s.io/finalizer"}
397 if _, err := clientSet.CoreV1().Pods(ns.Name).Create(tCtx, pod, metav1.CreateOptions{}); err != nil {
398 t.Errorf("Failed to create pod: %v", err)
399 }
400
401 pod.Status.Phase = tc.phase
402 addPodConditionReady(pod)
403 if _, err := clientSet.CoreV1().Pods(ns.Name).UpdateStatus(tCtx, pod, metav1.UpdateOptions{}); err != nil {
404 t.Fatal(err)
405 }
406
407 waitToObservePods(t, informers.Core().V1().Pods().Informer(), 1, tc.phase)
408 deleteOption := metav1.DeleteOptions{}
409 if tc.dryRun {
410 deleteOption.DryRun = []string{metav1.DryRunAll}
411 }
412
413 eviction := newV1Eviction(ns.Name, pod.Name, deleteOption)
414
415 err := clientSet.PolicyV1().Evictions(ns.Name).Evict(tCtx, eviction)
416 if err != nil {
417 t.Fatalf("Eviction of pod failed %v", err)
418 }
419
420 updatedPod, e := clientSet.CoreV1().Pods(ns.Name).Get(tCtx, pod.Name, metav1.GetOptions{})
421 if e != nil {
422 t.Fatalf("Failed to get the pod %q with error: %q", klog.KObj(pod), e)
423 }
424 _, cond := podutil.GetPodCondition(&updatedPod.Status, v1.PodConditionType(v1.DisruptionTarget))
425 if tc.wantDisruptionTargetCond && cond == nil {
426 t.Errorf("Pod %q does not have the expected condition: %q", klog.KObj(updatedPod), v1.DisruptionTarget)
427 } else if !tc.wantDisruptionTargetCond && cond != nil {
428 t.Errorf("Pod %q has an unexpected condition: %q", klog.KObj(updatedPod), v1.DisruptionTarget)
429 }
430 })
431 }
432 }
433
434
435 func TestEvictionWithUnhealthyPodEvictionPolicy(t *testing.T) {
436 cases := map[string]struct {
437 enableUnhealthyPodEvictionPolicy bool
438 unhealthyPodEvictionPolicy *policyv1.UnhealthyPodEvictionPolicyType
439 isPodReady bool
440 }{
441 "UnhealthyPodEvictionPolicy disabled and policy not set": {
442 enableUnhealthyPodEvictionPolicy: false,
443 unhealthyPodEvictionPolicy: nil,
444 isPodReady: true,
445 },
446 "UnhealthyPodEvictionPolicy enabled but policy not set": {
447 enableUnhealthyPodEvictionPolicy: true,
448 unhealthyPodEvictionPolicy: nil,
449 isPodReady: true,
450 },
451 "UnhealthyPodEvictionPolicy enabled but policy set to IfHealthyBudget with ready pod": {
452 enableUnhealthyPodEvictionPolicy: true,
453 unhealthyPodEvictionPolicy: unhealthyPolicyPtr(policyv1.IfHealthyBudget),
454 isPodReady: true,
455 },
456 "UnhealthyPodEvictionPolicy enabled but policy set to AlwaysAllow with ready pod": {
457 enableUnhealthyPodEvictionPolicy: true,
458 unhealthyPodEvictionPolicy: unhealthyPolicyPtr(policyv1.AlwaysAllow),
459 isPodReady: true,
460 },
461 "UnhealthyPodEvictionPolicy enabled but policy set to AlwaysAllow with unready pod": {
462 enableUnhealthyPodEvictionPolicy: true,
463 unhealthyPodEvictionPolicy: unhealthyPolicyPtr(policyv1.AlwaysAllow),
464 isPodReady: false,
465 },
466 }
467 for name, tc := range cases {
468 t.Run(name, func(t *testing.T) {
469 tCtx := ktesting.Init(t)
470 defer featuregatetesting.SetFeatureGateDuringTest(t, feature.DefaultFeatureGate, features.PDBUnhealthyPodEvictionPolicy, tc.enableUnhealthyPodEvictionPolicy)()
471 closeFn, rm, informers, _, clientSet := rmSetup(tCtx, t)
472 defer closeFn()
473
474 ns := framework.CreateNamespaceOrDie(clientSet, "eviction-with-pdb-pod-healthy-policy", t)
475 defer framework.DeleteNamespaceOrDie(clientSet, ns, t)
476 defer tCtx.Cancel("test has completed")
477
478 informers.Start(tCtx.Done())
479 go rm.Run(tCtx)
480
481 pod := newPod("pod")
482 if _, err := clientSet.CoreV1().Pods(ns.Name).Create(context.TODO(), pod, metav1.CreateOptions{}); err != nil {
483 t.Errorf("Failed to create pod: %v", err)
484 }
485
486 pod.Status.Phase = v1.PodRunning
487 if tc.isPodReady {
488 addPodConditionReady(pod)
489 }
490
491 if _, err := clientSet.CoreV1().Pods(ns.Name).UpdateStatus(tCtx, pod, metav1.UpdateOptions{}); err != nil {
492 t.Fatal(err)
493 }
494
495 waitToObservePods(t, informers.Core().V1().Pods().Informer(), 1, v1.PodRunning)
496
497 pdb := newPDB()
498 pdb.Spec.UnhealthyPodEvictionPolicy = tc.unhealthyPodEvictionPolicy
499 if _, err := clientSet.PolicyV1().PodDisruptionBudgets(ns.Name).Create(context.TODO(), pdb, metav1.CreateOptions{}); err != nil {
500 t.Errorf("Failed to create PodDisruptionBudget: %v", err)
501 }
502
503 if tc.isPodReady {
504 waitPDBStable(t, clientSet, ns.Name, pdb.Name, 1)
505 } else {
506 waitPDB(t, clientSet, ns.Name, pdb.Name, func(pdb *policyv1.PodDisruptionBudget) bool {
507 return pdb.Status.ExpectedPods == 1
508 })
509 }
510
511
512 policyV1NoRetriesRESTClient := &noRetriesRESTClient{Interface: clientSet.PolicyV1().RESTClient()}
513 policyV1NoRetriesClient := policyv1client.New(policyV1NoRetriesRESTClient)
514
515 deleteOption := metav1.DeleteOptions{}
516 eviction := newV1Eviction(ns.Name, pod.Name, deleteOption)
517 err := policyV1NoRetriesClient.Evictions(ns.Name).Evict(tCtx, eviction)
518 if err != nil {
519 t.Fatalf("Eviction of pod failed %v", err)
520 }
521 if policyV1NoRetriesRESTClient.postCalls != 1 {
522 t.Fatalf("expected a single POST call, got %d", policyV1NoRetriesRESTClient.postCalls)
523 }
524
525 waitToObservePods(t, informers.Core().V1().Pods().Informer(), 0, v1.PodRunning)
526 waitPDBStable(t, clientSet, ns.Name, pdb.Name, 0)
527 })
528 }
529 }
530
531
532 func TestEvictionWithPrecondition(t *testing.T) {
533 cases := map[string]struct {
534 enforceResourceVersion bool
535 injectWrongResourceVersion bool
536 enforceUID bool
537 injectWrongUID bool
538 shouldErr bool
539 }{
540 "eviction enforcing resource version": {
541 enforceResourceVersion: true,
542 },
543 "eviction enforcing UID": {
544 enforceUID: true,
545 },
546 "eviction enforcing resource version and UID": {
547 enforceUID: true,
548 enforceResourceVersion: true,
549 },
550 "eviction enforcing wrong resource version should fail": {
551 enforceResourceVersion: true,
552 injectWrongResourceVersion: true,
553 shouldErr: true,
554 },
555 "eviction enforcing wrong UID should fail": {
556 enforceUID: true,
557 injectWrongUID: true,
558 shouldErr: true,
559 },
560 }
561 for name, tc := range cases {
562 t.Run(name, func(t *testing.T) {
563 tCtx := ktesting.Init(t)
564 closeFn, rm, informers, _, clientSet := rmSetup(tCtx, t)
565 defer closeFn()
566
567 ns := framework.CreateNamespaceOrDie(clientSet, "eviction-with-preconditions", t)
568 defer framework.DeleteNamespaceOrDie(clientSet, ns, t)
569
570 defer tCtx.Cancel("test has completed")
571 informers.Start(tCtx.Done())
572 go rm.Run(tCtx)
573
574 pod := newPod("pod")
575 pod, err := clientSet.CoreV1().Pods(ns.Name).Create(context.TODO(), pod, metav1.CreateOptions{})
576 if err != nil {
577 t.Errorf("Failed to create pod: %q", err)
578 }
579
580 pod.Status.Phase = v1.PodRunning
581 addPodConditionReady(pod)
582
583
584 updatedPod, err := clientSet.CoreV1().Pods(ns.Name).UpdateStatus(tCtx, pod, metav1.UpdateOptions{})
585 if err != nil {
586 t.Fatal(err)
587 }
588
589 waitToObservePods(t, informers.Core().V1().Pods().Informer(), 1, v1.PodRunning)
590
591 deleteOption := metav1.DeleteOptions{}
592
593 if tc.enforceResourceVersion || tc.enforceUID {
594 deleteOption.Preconditions = &metav1.Preconditions{}
595 }
596
597 if tc.enforceResourceVersion {
598 if tc.injectWrongResourceVersion {
599 deleteOption.Preconditions.ResourceVersion = &pod.ResourceVersion
600 } else {
601 deleteOption.Preconditions.ResourceVersion = &updatedPod.ResourceVersion
602 }
603
604 }
605 if tc.enforceUID {
606 if tc.injectWrongUID {
607 newUID := uuid.NewUUID()
608 deleteOption.Preconditions.UID = &newUID
609 } else {
610 deleteOption.Preconditions.UID = &updatedPod.UID
611 }
612 }
613
614
615
616 policyV1NoRetriesRESTClient := &noRetriesRESTClient{Interface: clientSet.PolicyV1().RESTClient()}
617 policyV1NoRetriesClient := policyv1client.New(policyV1NoRetriesRESTClient)
618
619 eviction := newV1Eviction(ns.Name, updatedPod.Name, deleteOption)
620 err = policyV1NoRetriesClient.Evictions(ns.Name).Evict(tCtx, eviction)
621 if err != nil && !tc.shouldErr {
622 t.Fatalf("Eviction of pod failed %q", err)
623 }
624 if err == nil && tc.shouldErr {
625 t.Fatal("Eviction of pod should fail")
626 }
627 if policyV1NoRetriesRESTClient.postCalls != 1 {
628 t.Fatalf("expected a single POST call, got %d", policyV1NoRetriesRESTClient.postCalls)
629 }
630 })
631 }
632 }
633
634 func newPod(podName string) *v1.Pod {
635 return &v1.Pod{
636 ObjectMeta: metav1.ObjectMeta{
637 Name: podName,
638 Labels: map[string]string{"app": "test-evictions"},
639 },
640 Spec: v1.PodSpec{
641 Containers: []v1.Container{
642 {
643 Name: "fake-name",
644 Image: "fakeimage",
645 },
646 },
647 },
648 }
649 }
650
651 func addPodConditionReady(pod *v1.Pod) {
652 pod.Status.Conditions = append(pod.Status.Conditions, v1.PodCondition{
653 Type: v1.PodReady,
654 Status: v1.ConditionTrue,
655 })
656 }
657
658 func newPDB() *policyv1.PodDisruptionBudget {
659 return &policyv1.PodDisruptionBudget{
660 ObjectMeta: metav1.ObjectMeta{
661 Name: "test-pdb",
662 },
663 Spec: policyv1.PodDisruptionBudgetSpec{
664 MinAvailable: &intstr.IntOrString{
665 Type: intstr.Int,
666 IntVal: 0,
667 },
668 Selector: &metav1.LabelSelector{
669 MatchLabels: map[string]string{"app": "test-evictions"},
670 },
671 },
672 }
673 }
674
675 func newV1Eviction(ns, evictionName string, deleteOption metav1.DeleteOptions) *policyv1.Eviction {
676 return &policyv1.Eviction{
677 TypeMeta: metav1.TypeMeta{
678 APIVersion: "policy/v1",
679 Kind: "Eviction",
680 },
681 ObjectMeta: metav1.ObjectMeta{
682 Name: evictionName,
683 Namespace: ns,
684 },
685 DeleteOptions: &deleteOption,
686 }
687 }
688
689 func rmSetup(ctx context.Context, t *testing.T) (kubeapiservertesting.TearDownFunc, *disruption.DisruptionController, informers.SharedInformerFactory, *restclient.Config, clientset.Interface) {
690
691 server := kubeapiservertesting.StartTestServerOrDie(t, nil, []string{"--disable-admission-plugins=ServiceAccount"}, framework.SharedEtcd())
692
693 config := restclient.CopyConfig(server.ClientConfig)
694 clientSet, err := clientset.NewForConfig(config)
695 if err != nil {
696 t.Fatalf("Error in create clientset: %v", err)
697 }
698 resyncPeriod := 12 * time.Hour
699 informers := informers.NewSharedInformerFactory(clientset.NewForConfigOrDie(restclient.AddUserAgent(config, "pdb-informers")), resyncPeriod)
700
701 client := clientset.NewForConfigOrDie(restclient.AddUserAgent(config, "disruption-controller"))
702
703 discoveryClient := cacheddiscovery.NewMemCacheClient(clientSet.Discovery())
704 mapper := restmapper.NewDeferredDiscoveryRESTMapper(discoveryClient)
705
706 scaleKindResolver := scale.NewDiscoveryScaleKindResolver(client.Discovery())
707 scaleClient, err := scale.NewForConfig(config, mapper, dynamic.LegacyAPIPathResolverFunc, scaleKindResolver)
708 if err != nil {
709 t.Fatalf("Error in create scaleClient: %v", err)
710 }
711
712 rm := disruption.NewDisruptionController(
713 ctx,
714 informers.Core().V1().Pods(),
715 informers.Policy().V1().PodDisruptionBudgets(),
716 informers.Core().V1().ReplicationControllers(),
717 informers.Apps().V1().ReplicaSets(),
718 informers.Apps().V1().Deployments(),
719 informers.Apps().V1().StatefulSets(),
720 client,
721 mapper,
722 scaleClient,
723 client.Discovery(),
724 )
725 return server.TearDownFn, rm, informers, config, clientSet
726 }
727
728
729
730
731 func waitToObservePods(t *testing.T, podInformer cache.SharedIndexInformer, podNum int, phase v1.PodPhase) {
732 if err := wait.PollImmediate(2*time.Second, 60*time.Second, func() (bool, error) {
733 objects := podInformer.GetIndexer().List()
734 if len(objects) != podNum {
735 return false, nil
736 }
737 for _, obj := range objects {
738 pod := obj.(*v1.Pod)
739 if pod.Status.Phase != phase {
740 return false, nil
741 }
742 }
743 return true, nil
744 }); err != nil {
745 t.Fatal(err)
746 }
747 }
748
749 func waitPDBStable(t *testing.T, clientSet clientset.Interface, ns, pdbName string, podNum int32) {
750 waitPDB(t, clientSet, ns, pdbName, func(pdb *policyv1.PodDisruptionBudget) bool {
751 return pdb.Status.CurrentHealthy == podNum
752 })
753 }
754
755 func waitPDB(t *testing.T, clientSet clientset.Interface, ns, pdbName string, condition func(budget *policyv1.PodDisruptionBudget) bool) {
756 if err := wait.PollImmediate(2*time.Second, 60*time.Second, func() (bool, error) {
757 pdb, err := clientSet.PolicyV1().PodDisruptionBudgets(ns).Get(context.TODO(), pdbName, metav1.GetOptions{})
758 if err != nil {
759 return false, err
760 }
761 return condition(pdb), nil
762 }); err != nil {
763 t.Fatal(err)
764 }
765 }
766
767 func unhealthyPolicyPtr(unhealthyPodEvictionPolicy policyv1.UnhealthyPodEvictionPolicyType) *policyv1.UnhealthyPodEvictionPolicyType {
768 return &unhealthyPodEvictionPolicy
769 }
770
771 type noRetriesRESTClient struct {
772 mu sync.Mutex
773 postCalls int
774 restclient.Interface
775 }
776
777 func (n *noRetriesRESTClient) Post() *restclient.Request {
778 n.mu.Lock()
779 defer n.mu.Unlock()
780 n.postCalls++
781 return n.Interface.Post().MaxRetries(0)
782 }
783
View as plain text