mirror of
				https://github.com/optim-enterprises-bv/kubernetes.git
				synced 2025-11-04 04:08:16 +00:00 
			
		
		
		
	Merge pull request #116554 from atiratree/eviction-resource-version-fix
API-initiated eviction: handle deleteOptions correctly
This commit is contained in:
		@@ -25,6 +25,7 @@ import (
 | 
				
			|||||||
	policyv1 "k8s.io/api/policy/v1"
 | 
						policyv1 "k8s.io/api/policy/v1"
 | 
				
			||||||
	policyv1beta1 "k8s.io/api/policy/v1beta1"
 | 
						policyv1beta1 "k8s.io/api/policy/v1beta1"
 | 
				
			||||||
	"k8s.io/apimachinery/pkg/api/errors"
 | 
						"k8s.io/apimachinery/pkg/api/errors"
 | 
				
			||||||
 | 
						"k8s.io/apimachinery/pkg/api/meta"
 | 
				
			||||||
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 | 
						metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 | 
				
			||||||
	"k8s.io/apimachinery/pkg/labels"
 | 
						"k8s.io/apimachinery/pkg/labels"
 | 
				
			||||||
	"k8s.io/apimachinery/pkg/runtime"
 | 
						"k8s.io/apimachinery/pkg/runtime"
 | 
				
			||||||
@@ -308,11 +309,30 @@ func (r *EvictionREST) Create(ctx context.Context, name string, obj runtime.Obje
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func addConditionAndDeletePod(r *EvictionREST, ctx context.Context, name string, validation rest.ValidateObjectFunc, options *metav1.DeleteOptions) error {
 | 
					func addConditionAndDeletePod(r *EvictionREST, ctx context.Context, name string, validation rest.ValidateObjectFunc, options *metav1.DeleteOptions) error {
 | 
				
			||||||
	if feature.DefaultFeatureGate.Enabled(features.PodDisruptionConditions) {
 | 
						if !dryrun.IsDryRun(options.DryRun) && feature.DefaultFeatureGate.Enabled(features.PodDisruptionConditions) {
 | 
				
			||||||
		pod, err := getPod(r, ctx, name)
 | 
							getLatestPod := func(_ context.Context, _, oldObj runtime.Object) (runtime.Object, error) {
 | 
				
			||||||
		if err != nil {
 | 
								// Throwaway the newObj. We care only about the latest pod obtained from etcd (oldObj).
 | 
				
			||||||
			return err
 | 
								// So we can add DisruptionTarget condition in conditionAppender without conflicts.
 | 
				
			||||||
 | 
								latestPod := oldObj.(*api.Pod).DeepCopy()
 | 
				
			||||||
 | 
								if options.Preconditions != nil {
 | 
				
			||||||
 | 
									if uid := options.Preconditions.UID; uid != nil && len(*uid) > 0 && *uid != latestPod.UID {
 | 
				
			||||||
 | 
										return nil, errors.NewConflict(
 | 
				
			||||||
 | 
											schema.GroupResource{Group: "", Resource: "Pod"},
 | 
				
			||||||
 | 
											latestPod.Name,
 | 
				
			||||||
 | 
											fmt.Errorf("the UID in the precondition (%s) does not match the UID in record (%s). The object might have been deleted and then recreated", *uid, latestPod.UID),
 | 
				
			||||||
 | 
										)
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
									if rv := options.Preconditions.ResourceVersion; rv != nil && len(*rv) > 0 && *rv != latestPod.ResourceVersion {
 | 
				
			||||||
 | 
										return nil, errors.NewConflict(
 | 
				
			||||||
 | 
											schema.GroupResource{Group: "", Resource: "Pod"},
 | 
				
			||||||
 | 
											latestPod.Name,
 | 
				
			||||||
 | 
											fmt.Errorf("the ResourceVersion in the precondition (%s) does not match the ResourceVersion in record (%s). The object might have been modified", *rv, latestPod.ResourceVersion),
 | 
				
			||||||
 | 
										)
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								return latestPod, nil
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		conditionAppender := func(_ context.Context, newObj, _ runtime.Object) (runtime.Object, error) {
 | 
							conditionAppender := func(_ context.Context, newObj, _ runtime.Object) (runtime.Object, error) {
 | 
				
			||||||
			podObj := newObj.(*api.Pod)
 | 
								podObj := newObj.(*api.Pod)
 | 
				
			||||||
			podutil.UpdatePodCondition(&podObj.Status, &api.PodCondition{
 | 
								podutil.UpdatePodCondition(&podObj.Status, &api.PodCondition{
 | 
				
			||||||
@@ -324,11 +344,22 @@ func addConditionAndDeletePod(r *EvictionREST, ctx context.Context, name string,
 | 
				
			|||||||
			return podObj, nil
 | 
								return podObj, nil
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		podCopyUpdated := rest.DefaultUpdatedObjectInfo(pod, conditionAppender)
 | 
							podUpdatedObjectInfo := rest.DefaultUpdatedObjectInfo(nil, getLatestPod, conditionAppender) // order important
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		if _, _, err = r.store.Update(ctx, name, podCopyUpdated, rest.ValidateAllObjectFunc, rest.ValidateAllObjectUpdateFunc, false, &metav1.UpdateOptions{}); err != nil {
 | 
							updatedPodObject, _, err := r.store.Update(ctx, name, podUpdatedObjectInfo, rest.ValidateAllObjectFunc, rest.ValidateAllObjectUpdateFunc, false, &metav1.UpdateOptions{})
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
			return err
 | 
								return err
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if !resourceVersionIsUnset(options) {
 | 
				
			||||||
 | 
								newResourceVersion, err := meta.NewAccessor().ResourceVersion(updatedPodObject)
 | 
				
			||||||
 | 
								if err != nil {
 | 
				
			||||||
 | 
									return err
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								// bump the resource version, since we are the one who modified it via the update
 | 
				
			||||||
 | 
								options = options.DeepCopy()
 | 
				
			||||||
 | 
								options.Preconditions.ResourceVersion = &newResourceVersion
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	_, _, err := r.store.Delete(ctx, name, rest.ValidateAllObjectFunc, options)
 | 
						_, _, err := r.store.Delete(ctx, name, rest.ValidateAllObjectFunc, options)
 | 
				
			||||||
	return err
 | 
						return err
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -709,6 +709,109 @@ func TestEvictionPDBStatus(t *testing.T) {
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func TestAddConditionAndDelete(t *testing.T) {
 | 
				
			||||||
 | 
						cases := []struct {
 | 
				
			||||||
 | 
							name              string
 | 
				
			||||||
 | 
							initialPod        bool
 | 
				
			||||||
 | 
							makeDeleteOptions func(*api.Pod) *metav1.DeleteOptions
 | 
				
			||||||
 | 
							expectErr         string
 | 
				
			||||||
 | 
						}{
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name:              "simple",
 | 
				
			||||||
 | 
								initialPod:        true,
 | 
				
			||||||
 | 
								makeDeleteOptions: func(pod *api.Pod) *metav1.DeleteOptions { return &metav1.DeleteOptions{} },
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name:              "missing",
 | 
				
			||||||
 | 
								initialPod:        false,
 | 
				
			||||||
 | 
								makeDeleteOptions: func(pod *api.Pod) *metav1.DeleteOptions { return &metav1.DeleteOptions{} },
 | 
				
			||||||
 | 
								expectErr:         "not found",
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name:       "valid uid",
 | 
				
			||||||
 | 
								initialPod: true,
 | 
				
			||||||
 | 
								makeDeleteOptions: func(pod *api.Pod) *metav1.DeleteOptions {
 | 
				
			||||||
 | 
									return &metav1.DeleteOptions{Preconditions: &metav1.Preconditions{UID: &pod.UID}}
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name:       "invalid uid",
 | 
				
			||||||
 | 
								initialPod: true,
 | 
				
			||||||
 | 
								makeDeleteOptions: func(pod *api.Pod) *metav1.DeleteOptions {
 | 
				
			||||||
 | 
									badUID := pod.UID + "1"
 | 
				
			||||||
 | 
									return &metav1.DeleteOptions{Preconditions: &metav1.Preconditions{UID: &badUID}}
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								expectErr: "The object might have been deleted and then recreated",
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name:       "valid resourceVersion",
 | 
				
			||||||
 | 
								initialPod: true,
 | 
				
			||||||
 | 
								makeDeleteOptions: func(pod *api.Pod) *metav1.DeleteOptions {
 | 
				
			||||||
 | 
									return &metav1.DeleteOptions{Preconditions: &metav1.Preconditions{ResourceVersion: &pod.ResourceVersion}}
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name:       "invalid resourceVersion",
 | 
				
			||||||
 | 
								initialPod: true,
 | 
				
			||||||
 | 
								makeDeleteOptions: func(pod *api.Pod) *metav1.DeleteOptions {
 | 
				
			||||||
 | 
									badRV := pod.ResourceVersion + "1"
 | 
				
			||||||
 | 
									return &metav1.DeleteOptions{Preconditions: &metav1.Preconditions{ResourceVersion: &badRV}}
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								expectErr: "The object might have been modified",
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						testContext := genericapirequest.WithNamespace(genericapirequest.NewContext(), metav1.NamespaceDefault)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						storage, _, _, server := newStorage(t)
 | 
				
			||||||
 | 
						defer server.Terminate(t)
 | 
				
			||||||
 | 
						defer storage.Store.DestroyFunc()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						client := fake.NewSimpleClientset()
 | 
				
			||||||
 | 
						evictionRest := newEvictionStorage(storage.Store, client.PolicyV1())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for _, tc := range cases {
 | 
				
			||||||
 | 
							for _, conditionsEnabled := range []bool{true, false} {
 | 
				
			||||||
 | 
								name := fmt.Sprintf("%s_conditions=%v", tc.name, conditionsEnabled)
 | 
				
			||||||
 | 
								t.Run(name, func(t *testing.T) {
 | 
				
			||||||
 | 
									defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.PodDisruptionConditions, conditionsEnabled)()
 | 
				
			||||||
 | 
									var deleteOptions *metav1.DeleteOptions
 | 
				
			||||||
 | 
									if tc.initialPod {
 | 
				
			||||||
 | 
										newPod := validNewPod()
 | 
				
			||||||
 | 
										createdObj, err := storage.Create(testContext, newPod, rest.ValidateAllObjectFunc, &metav1.CreateOptions{})
 | 
				
			||||||
 | 
										if err != nil {
 | 
				
			||||||
 | 
											t.Fatal(err)
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
										t.Cleanup(func() {
 | 
				
			||||||
 | 
											zero := int64(0)
 | 
				
			||||||
 | 
											storage.Delete(testContext, newPod.Name, rest.ValidateAllObjectFunc, &metav1.DeleteOptions{GracePeriodSeconds: &zero})
 | 
				
			||||||
 | 
										})
 | 
				
			||||||
 | 
										deleteOptions = tc.makeDeleteOptions(createdObj.(*api.Pod))
 | 
				
			||||||
 | 
									} else {
 | 
				
			||||||
 | 
										deleteOptions = tc.makeDeleteOptions(nil)
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
									if deleteOptions == nil {
 | 
				
			||||||
 | 
										deleteOptions = &metav1.DeleteOptions{}
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									err := addConditionAndDeletePod(evictionRest, testContext, "foo", rest.ValidateAllObjectFunc, deleteOptions)
 | 
				
			||||||
 | 
									if err == nil {
 | 
				
			||||||
 | 
										if tc.expectErr != "" {
 | 
				
			||||||
 | 
											t.Fatalf("expected err containing %q, got none", tc.expectErr)
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
										return
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
									if tc.expectErr == "" {
 | 
				
			||||||
 | 
										t.Fatalf("unexpected err: %v", err)
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
									if !strings.Contains(err.Error(), tc.expectErr) {
 | 
				
			||||||
 | 
										t.Fatalf("expected err containing %q, got %v", tc.expectErr, err)
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								})
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func resource(resource string) schema.GroupResource {
 | 
					func resource(resource string) schema.GroupResource {
 | 
				
			||||||
	return schema.GroupResource{Group: "", Resource: resource}
 | 
						return schema.GroupResource{Group: "", Resource: resource}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -765,7 +868,7 @@ func (ms *mockStore) Watch(ctx context.Context, options *metainternalversion.Lis
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (ms *mockStore) Update(ctx context.Context, name string, objInfo rest.UpdatedObjectInfo, createValidation rest.ValidateObjectFunc, updateValidation rest.ValidateObjectUpdateFunc, forceAllowCreate bool, options *metav1.UpdateOptions) (runtime.Object, bool, error) {
 | 
					func (ms *mockStore) Update(ctx context.Context, name string, objInfo rest.UpdatedObjectInfo, createValidation rest.ValidateObjectFunc, updateValidation rest.ValidateObjectUpdateFunc, forceAllowCreate bool, options *metav1.UpdateOptions) (runtime.Object, bool, error) {
 | 
				
			||||||
	return nil, false, nil
 | 
						return ms.pod, false, nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (ms *mockStore) Get(ctx context.Context, name string, options *metav1.GetOptions) (runtime.Object, error) {
 | 
					func (ms *mockStore) Get(ctx context.Context, name string, options *metav1.GetOptions) (runtime.Object, error) {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -38,12 +38,14 @@ import (
 | 
				
			|||||||
	"k8s.io/apimachinery/pkg/types"
 | 
						"k8s.io/apimachinery/pkg/types"
 | 
				
			||||||
	utilerrors "k8s.io/apimachinery/pkg/util/errors"
 | 
						utilerrors "k8s.io/apimachinery/pkg/util/errors"
 | 
				
			||||||
	"k8s.io/apimachinery/pkg/util/intstr"
 | 
						"k8s.io/apimachinery/pkg/util/intstr"
 | 
				
			||||||
 | 
						"k8s.io/apimachinery/pkg/util/uuid"
 | 
				
			||||||
	"k8s.io/apimachinery/pkg/util/wait"
 | 
						"k8s.io/apimachinery/pkg/util/wait"
 | 
				
			||||||
	"k8s.io/apiserver/pkg/util/feature"
 | 
						"k8s.io/apiserver/pkg/util/feature"
 | 
				
			||||||
	cacheddiscovery "k8s.io/client-go/discovery/cached/memory"
 | 
						cacheddiscovery "k8s.io/client-go/discovery/cached/memory"
 | 
				
			||||||
	"k8s.io/client-go/dynamic"
 | 
						"k8s.io/client-go/dynamic"
 | 
				
			||||||
	"k8s.io/client-go/informers"
 | 
						"k8s.io/client-go/informers"
 | 
				
			||||||
	clientset "k8s.io/client-go/kubernetes"
 | 
						clientset "k8s.io/client-go/kubernetes"
 | 
				
			||||||
 | 
						policyv1client "k8s.io/client-go/kubernetes/typed/policy/v1"
 | 
				
			||||||
	restclient "k8s.io/client-go/rest"
 | 
						restclient "k8s.io/client-go/rest"
 | 
				
			||||||
	"k8s.io/client-go/restmapper"
 | 
						"k8s.io/client-go/restmapper"
 | 
				
			||||||
	"k8s.io/client-go/scale"
 | 
						"k8s.io/client-go/scale"
 | 
				
			||||||
@@ -345,22 +347,34 @@ func TestEvictionWithFinalizers(t *testing.T) {
 | 
				
			|||||||
	cases := map[string]struct {
 | 
						cases := map[string]struct {
 | 
				
			||||||
		enablePodDisruptionConditions bool
 | 
							enablePodDisruptionConditions bool
 | 
				
			||||||
		phase                         v1.PodPhase
 | 
							phase                         v1.PodPhase
 | 
				
			||||||
 | 
							dryRun                        bool
 | 
				
			||||||
 | 
							wantDisruptionTargetCond      bool
 | 
				
			||||||
	}{
 | 
						}{
 | 
				
			||||||
		"terminal pod with PodDisruptionConditions enabled": {
 | 
							"terminal pod with PodDisruptionConditions enabled": {
 | 
				
			||||||
			enablePodDisruptionConditions: true,
 | 
								enablePodDisruptionConditions: true,
 | 
				
			||||||
			phase:                         v1.PodSucceeded,
 | 
								phase:                         v1.PodSucceeded,
 | 
				
			||||||
 | 
								wantDisruptionTargetCond:      true,
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
		"terminal pod with PodDisruptionConditions disabled": {
 | 
							"terminal pod with PodDisruptionConditions disabled": {
 | 
				
			||||||
			enablePodDisruptionConditions: false,
 | 
								enablePodDisruptionConditions: false,
 | 
				
			||||||
			phase:                         v1.PodSucceeded,
 | 
								phase:                         v1.PodSucceeded,
 | 
				
			||||||
 | 
								wantDisruptionTargetCond:      false,
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
		"running pod with PodDisruptionConditions enabled": {
 | 
							"running pod with PodDisruptionConditions enabled": {
 | 
				
			||||||
			enablePodDisruptionConditions: true,
 | 
								enablePodDisruptionConditions: true,
 | 
				
			||||||
			phase:                         v1.PodRunning,
 | 
								phase:                         v1.PodRunning,
 | 
				
			||||||
 | 
								wantDisruptionTargetCond:      true,
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
		"running pod with PodDisruptionConditions disabled": {
 | 
							"running pod with PodDisruptionConditions disabled": {
 | 
				
			||||||
			enablePodDisruptionConditions: false,
 | 
								enablePodDisruptionConditions: false,
 | 
				
			||||||
			phase:                         v1.PodRunning,
 | 
								phase:                         v1.PodRunning,
 | 
				
			||||||
 | 
								wantDisruptionTargetCond:      false,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							"running pod with PodDisruptionConditions enabled should not update conditions in dry-run mode": {
 | 
				
			||||||
 | 
								enablePodDisruptionConditions: true,
 | 
				
			||||||
 | 
								phase:                         v1.PodRunning,
 | 
				
			||||||
 | 
								dryRun:                        true,
 | 
				
			||||||
 | 
								wantDisruptionTargetCond:      false,
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	for name, tc := range cases {
 | 
						for name, tc := range cases {
 | 
				
			||||||
@@ -391,6 +405,9 @@ func TestEvictionWithFinalizers(t *testing.T) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
			waitToObservePods(t, informers.Core().V1().Pods().Informer(), 1, tc.phase)
 | 
								waitToObservePods(t, informers.Core().V1().Pods().Informer(), 1, tc.phase)
 | 
				
			||||||
			deleteOption := metav1.DeleteOptions{}
 | 
								deleteOption := metav1.DeleteOptions{}
 | 
				
			||||||
 | 
								if tc.dryRun {
 | 
				
			||||||
 | 
									deleteOption.DryRun = []string{metav1.DryRunAll}
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			eviction := newV1Eviction(ns.Name, pod.Name, deleteOption)
 | 
								eviction := newV1Eviction(ns.Name, pod.Name, deleteOption)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -404,9 +421,9 @@ func TestEvictionWithFinalizers(t *testing.T) {
 | 
				
			|||||||
				t.Fatalf("Failed to get the pod %q with error: %q", klog.KObj(pod), e)
 | 
									t.Fatalf("Failed to get the pod %q with error: %q", klog.KObj(pod), e)
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
			_, cond := podutil.GetPodCondition(&updatedPod.Status, v1.PodConditionType(v1.DisruptionTarget))
 | 
								_, cond := podutil.GetPodCondition(&updatedPod.Status, v1.PodConditionType(v1.DisruptionTarget))
 | 
				
			||||||
			if tc.enablePodDisruptionConditions == true && cond == nil {
 | 
								if tc.wantDisruptionTargetCond == true && cond == nil {
 | 
				
			||||||
				t.Errorf("Pod %q does not have the expected condition: %q", klog.KObj(updatedPod), v1.DisruptionTarget)
 | 
									t.Errorf("Pod %q does not have the expected condition: %q", klog.KObj(updatedPod), v1.DisruptionTarget)
 | 
				
			||||||
			} else if tc.enablePodDisruptionConditions == false && cond != nil {
 | 
								} else if tc.wantDisruptionTargetCond == false && cond != nil {
 | 
				
			||||||
				t.Errorf("Pod %q has an unexpected condition: %q", klog.KObj(updatedPod), v1.DisruptionTarget)
 | 
									t.Errorf("Pod %q has an unexpected condition: %q", klog.KObj(updatedPod), v1.DisruptionTarget)
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
		})
 | 
							})
 | 
				
			||||||
@@ -489,12 +506,20 @@ func TestEvictionWithUnhealthyPodEvictionPolicy(t *testing.T) {
 | 
				
			|||||||
					return pdb.Status.ExpectedPods == 1
 | 
										return pdb.Status.ExpectedPods == 1
 | 
				
			||||||
				})
 | 
									})
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 | 
								// Eviction API can potentially return http.StatusTooManyRequests (429) or http.StatusGatewayTimeout (504) with retryAfterSeconds == 10s
 | 
				
			||||||
 | 
								// Do not retry - we want to test that the first request succeeds and make sure it doesn't unnecessarily block the test for 10s
 | 
				
			||||||
 | 
								policyV1NoRetriesRESTClient := &noRetriesRESTClient{Interface: clientSet.PolicyV1().RESTClient()}
 | 
				
			||||||
 | 
								policyV1NoRetriesClient := policyv1client.New(policyV1NoRetriesRESTClient)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			deleteOption := metav1.DeleteOptions{}
 | 
								deleteOption := metav1.DeleteOptions{}
 | 
				
			||||||
			eviction := newV1Eviction(ns.Name, pod.Name, deleteOption)
 | 
								eviction := newV1Eviction(ns.Name, pod.Name, deleteOption)
 | 
				
			||||||
			err := clientSet.PolicyV1().Evictions(ns.Name).Evict(ctx, eviction)
 | 
								err := policyV1NoRetriesClient.Evictions(ns.Name).Evict(ctx, eviction)
 | 
				
			||||||
			if err != nil {
 | 
								if err != nil {
 | 
				
			||||||
				t.Fatalf("Eviction of pod failed %v", err)
 | 
									t.Fatalf("Eviction of pod failed %v", err)
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 | 
								if policyV1NoRetriesRESTClient.postCalls != 1 {
 | 
				
			||||||
 | 
									t.Fatalf("expected a single POST call, got %d", policyV1NoRetriesRESTClient.postCalls)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			waitToObservePods(t, informers.Core().V1().Pods().Informer(), 0, v1.PodRunning)
 | 
								waitToObservePods(t, informers.Core().V1().Pods().Informer(), 0, v1.PodRunning)
 | 
				
			||||||
			waitPDBStable(t, clientSet, ns.Name, pdb.Name, 0)
 | 
								waitPDBStable(t, clientSet, ns.Name, pdb.Name, 0)
 | 
				
			||||||
@@ -502,6 +527,109 @@ func TestEvictionWithUnhealthyPodEvictionPolicy(t *testing.T) {
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// TestEvictionWithPrecondition tests eviction with delete preconditions
 | 
				
			||||||
 | 
					func TestEvictionWithPrecondition(t *testing.T) {
 | 
				
			||||||
 | 
						cases := map[string]struct {
 | 
				
			||||||
 | 
							enforceResourceVersion     bool
 | 
				
			||||||
 | 
							injectWrongResourceVersion bool
 | 
				
			||||||
 | 
							enforceUID                 bool
 | 
				
			||||||
 | 
							injectWrongUID             bool
 | 
				
			||||||
 | 
							shouldErr                  bool
 | 
				
			||||||
 | 
						}{
 | 
				
			||||||
 | 
							"eviction enforcing resource version": {
 | 
				
			||||||
 | 
								enforceResourceVersion: true,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							"eviction enforcing UID": {
 | 
				
			||||||
 | 
								enforceUID: true,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							"eviction enforcing resource version and UID": {
 | 
				
			||||||
 | 
								enforceUID:             true,
 | 
				
			||||||
 | 
								enforceResourceVersion: true,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							"eviction enforcing wrong resource version should fail": {
 | 
				
			||||||
 | 
								enforceResourceVersion:     true,
 | 
				
			||||||
 | 
								injectWrongResourceVersion: true,
 | 
				
			||||||
 | 
								shouldErr:                  true,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							"eviction enforcing wrong UID should fail": {
 | 
				
			||||||
 | 
								enforceUID:     true,
 | 
				
			||||||
 | 
								injectWrongUID: true,
 | 
				
			||||||
 | 
								shouldErr:      true,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						for name, tc := range cases {
 | 
				
			||||||
 | 
							t.Run(name, func(t *testing.T) {
 | 
				
			||||||
 | 
								closeFn, rm, informers, _, clientSet := rmSetup(t)
 | 
				
			||||||
 | 
								defer closeFn()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								ns := framework.CreateNamespaceOrDie(clientSet, "eviction-with-preconditions", t)
 | 
				
			||||||
 | 
								defer framework.DeleteNamespaceOrDie(clientSet, ns, t)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								ctx, cancel := context.WithCancel(context.Background())
 | 
				
			||||||
 | 
								defer cancel()
 | 
				
			||||||
 | 
								informers.Start(ctx.Done())
 | 
				
			||||||
 | 
								go rm.Run(ctx)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								pod := newPod("pod")
 | 
				
			||||||
 | 
								pod, err := clientSet.CoreV1().Pods(ns.Name).Create(context.TODO(), pod, metav1.CreateOptions{})
 | 
				
			||||||
 | 
								if err != nil {
 | 
				
			||||||
 | 
									t.Errorf("Failed to create pod: %q", err)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								pod.Status.Phase = v1.PodRunning
 | 
				
			||||||
 | 
								addPodConditionReady(pod)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								// generate a new resource version
 | 
				
			||||||
 | 
								updatedPod, err := clientSet.CoreV1().Pods(ns.Name).UpdateStatus(ctx, pod, metav1.UpdateOptions{})
 | 
				
			||||||
 | 
								if err != nil {
 | 
				
			||||||
 | 
									t.Fatal(err)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								waitToObservePods(t, informers.Core().V1().Pods().Informer(), 1, v1.PodRunning)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								deleteOption := metav1.DeleteOptions{}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								if tc.enforceResourceVersion || tc.enforceUID {
 | 
				
			||||||
 | 
									deleteOption.Preconditions = &metav1.Preconditions{}
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								if tc.enforceResourceVersion {
 | 
				
			||||||
 | 
									if tc.injectWrongResourceVersion {
 | 
				
			||||||
 | 
										deleteOption.Preconditions.ResourceVersion = &pod.ResourceVersion
 | 
				
			||||||
 | 
									} else {
 | 
				
			||||||
 | 
										deleteOption.Preconditions.ResourceVersion = &updatedPod.ResourceVersion
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								if tc.enforceUID {
 | 
				
			||||||
 | 
									if tc.injectWrongUID {
 | 
				
			||||||
 | 
										newUID := uuid.NewUUID()
 | 
				
			||||||
 | 
										deleteOption.Preconditions.UID = &newUID
 | 
				
			||||||
 | 
									} else {
 | 
				
			||||||
 | 
										deleteOption.Preconditions.UID = &updatedPod.UID
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								// Eviction API can potentially return http.StatusTooManyRequests (429) or http.StatusGatewayTimeout (504) with retryAfterSeconds == 10s
 | 
				
			||||||
 | 
								// Do not retry - we want to test that the first request succeeds and make sure it doesn't unnecessarily block the test for 10s
 | 
				
			||||||
 | 
								policyV1NoRetriesRESTClient := &noRetriesRESTClient{Interface: clientSet.PolicyV1().RESTClient()}
 | 
				
			||||||
 | 
								policyV1NoRetriesClient := policyv1client.New(policyV1NoRetriesRESTClient)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								eviction := newV1Eviction(ns.Name, updatedPod.Name, deleteOption)
 | 
				
			||||||
 | 
								err = policyV1NoRetriesClient.Evictions(ns.Name).Evict(ctx, eviction)
 | 
				
			||||||
 | 
								if err != nil && !tc.shouldErr {
 | 
				
			||||||
 | 
									t.Fatalf("Eviction of pod failed %q", err)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								if err == nil && tc.shouldErr {
 | 
				
			||||||
 | 
									t.Fatal("Eviction of pod should fail")
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								if policyV1NoRetriesRESTClient.postCalls != 1 {
 | 
				
			||||||
 | 
									t.Fatalf("expected a single POST call, got %d", policyV1NoRetriesRESTClient.postCalls)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func newPod(podName string) *v1.Pod {
 | 
					func newPod(podName string) *v1.Pod {
 | 
				
			||||||
	return &v1.Pod{
 | 
						return &v1.Pod{
 | 
				
			||||||
		ObjectMeta: metav1.ObjectMeta{
 | 
							ObjectMeta: metav1.ObjectMeta{
 | 
				
			||||||
@@ -637,3 +765,16 @@ func waitPDB(t *testing.T, clientSet clientset.Interface, ns, pdbName string, co
 | 
				
			|||||||
func unhealthyPolicyPtr(unhealthyPodEvictionPolicy policyv1.UnhealthyPodEvictionPolicyType) *policyv1.UnhealthyPodEvictionPolicyType {
 | 
					func unhealthyPolicyPtr(unhealthyPodEvictionPolicy policyv1.UnhealthyPodEvictionPolicyType) *policyv1.UnhealthyPodEvictionPolicyType {
 | 
				
			||||||
	return &unhealthyPodEvictionPolicy
 | 
						return &unhealthyPodEvictionPolicy
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type noRetriesRESTClient struct {
 | 
				
			||||||
 | 
						mu        sync.Mutex
 | 
				
			||||||
 | 
						postCalls int
 | 
				
			||||||
 | 
						restclient.Interface
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (n *noRetriesRESTClient) Post() *restclient.Request {
 | 
				
			||||||
 | 
						n.mu.Lock()
 | 
				
			||||||
 | 
						defer n.mu.Unlock()
 | 
				
			||||||
 | 
						n.postCalls++
 | 
				
			||||||
 | 
						return n.Interface.Post().MaxRetries(0)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user