Skip to content

Commit fe91bc2

Browse files
authored
Merge pull request kubernetes#116554 from atiratree/eviction-resource-version-fix
API-initiated eviction: handle deleteOptions correctly
2 parents c14e098 + 51c0e23 commit fe91bc2

File tree

3 files changed

+285
-10
lines changed

3 files changed

+285
-10
lines changed

pkg/registry/core/pod/storage/eviction.go

Lines changed: 37 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import (
2525
policyv1 "k8s.io/api/policy/v1"
2626
policyv1beta1 "k8s.io/api/policy/v1beta1"
2727
"k8s.io/apimachinery/pkg/api/errors"
28+
"k8s.io/apimachinery/pkg/api/meta"
2829
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2930
"k8s.io/apimachinery/pkg/labels"
3031
"k8s.io/apimachinery/pkg/runtime"
@@ -308,11 +309,30 @@ func (r *EvictionREST) Create(ctx context.Context, name string, obj runtime.Obje
308309
}
309310

310311
func addConditionAndDeletePod(r *EvictionREST, ctx context.Context, name string, validation rest.ValidateObjectFunc, options *metav1.DeleteOptions) error {
311-
if feature.DefaultFeatureGate.Enabled(features.PodDisruptionConditions) {
312-
pod, err := getPod(r, ctx, name)
313-
if err != nil {
314-
return err
312+
if !dryrun.IsDryRun(options.DryRun) && feature.DefaultFeatureGate.Enabled(features.PodDisruptionConditions) {
313+
getLatestPod := func(_ context.Context, _, oldObj runtime.Object) (runtime.Object, error) {
314+
// Throwaway the newObj. We care only about the latest pod obtained from etcd (oldObj).
315+
// So we can add DisruptionTarget condition in conditionAppender without conflicts.
316+
latestPod := oldObj.(*api.Pod).DeepCopy()
317+
if options.Preconditions != nil {
318+
if uid := options.Preconditions.UID; uid != nil && len(*uid) > 0 && *uid != latestPod.UID {
319+
return nil, errors.NewConflict(
320+
schema.GroupResource{Group: "", Resource: "Pod"},
321+
latestPod.Name,
322+
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),
323+
)
324+
}
325+
if rv := options.Preconditions.ResourceVersion; rv != nil && len(*rv) > 0 && *rv != latestPod.ResourceVersion {
326+
return nil, errors.NewConflict(
327+
schema.GroupResource{Group: "", Resource: "Pod"},
328+
latestPod.Name,
329+
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),
330+
)
331+
}
332+
}
333+
return latestPod, nil
315334
}
335+
316336
conditionAppender := func(_ context.Context, newObj, _ runtime.Object) (runtime.Object, error) {
317337
podObj := newObj.(*api.Pod)
318338
podutil.UpdatePodCondition(&podObj.Status, &api.PodCondition{
@@ -324,11 +344,22 @@ func addConditionAndDeletePod(r *EvictionREST, ctx context.Context, name string,
324344
return podObj, nil
325345
}
326346

327-
podCopyUpdated := rest.DefaultUpdatedObjectInfo(pod, conditionAppender)
347+
podUpdatedObjectInfo := rest.DefaultUpdatedObjectInfo(nil, getLatestPod, conditionAppender) // order important
328348

329-
if _, _, err = r.store.Update(ctx, name, podCopyUpdated, rest.ValidateAllObjectFunc, rest.ValidateAllObjectUpdateFunc, false, &metav1.UpdateOptions{}); err != nil {
349+
updatedPodObject, _, err := r.store.Update(ctx, name, podUpdatedObjectInfo, rest.ValidateAllObjectFunc, rest.ValidateAllObjectUpdateFunc, false, &metav1.UpdateOptions{})
350+
if err != nil {
330351
return err
331352
}
353+
354+
if !resourceVersionIsUnset(options) {
355+
newResourceVersion, err := meta.NewAccessor().ResourceVersion(updatedPodObject)
356+
if err != nil {
357+
return err
358+
}
359+
// bump the resource version, since we are the one who modified it via the update
360+
options = options.DeepCopy()
361+
options.Preconditions.ResourceVersion = &newResourceVersion
362+
}
332363
}
333364
_, _, err := r.store.Delete(ctx, name, rest.ValidateAllObjectFunc, options)
334365
return err

pkg/registry/core/pod/storage/eviction_test.go

Lines changed: 104 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -709,6 +709,109 @@ func TestEvictionPDBStatus(t *testing.T) {
709709
}
710710
}
711711

712+
func TestAddConditionAndDelete(t *testing.T) {
713+
cases := []struct {
714+
name string
715+
initialPod bool
716+
makeDeleteOptions func(*api.Pod) *metav1.DeleteOptions
717+
expectErr string
718+
}{
719+
{
720+
name: "simple",
721+
initialPod: true,
722+
makeDeleteOptions: func(pod *api.Pod) *metav1.DeleteOptions { return &metav1.DeleteOptions{} },
723+
},
724+
{
725+
name: "missing",
726+
initialPod: false,
727+
makeDeleteOptions: func(pod *api.Pod) *metav1.DeleteOptions { return &metav1.DeleteOptions{} },
728+
expectErr: "not found",
729+
},
730+
{
731+
name: "valid uid",
732+
initialPod: true,
733+
makeDeleteOptions: func(pod *api.Pod) *metav1.DeleteOptions {
734+
return &metav1.DeleteOptions{Preconditions: &metav1.Preconditions{UID: &pod.UID}}
735+
},
736+
},
737+
{
738+
name: "invalid uid",
739+
initialPod: true,
740+
makeDeleteOptions: func(pod *api.Pod) *metav1.DeleteOptions {
741+
badUID := pod.UID + "1"
742+
return &metav1.DeleteOptions{Preconditions: &metav1.Preconditions{UID: &badUID}}
743+
},
744+
expectErr: "The object might have been deleted and then recreated",
745+
},
746+
{
747+
name: "valid resourceVersion",
748+
initialPod: true,
749+
makeDeleteOptions: func(pod *api.Pod) *metav1.DeleteOptions {
750+
return &metav1.DeleteOptions{Preconditions: &metav1.Preconditions{ResourceVersion: &pod.ResourceVersion}}
751+
},
752+
},
753+
{
754+
name: "invalid resourceVersion",
755+
initialPod: true,
756+
makeDeleteOptions: func(pod *api.Pod) *metav1.DeleteOptions {
757+
badRV := pod.ResourceVersion + "1"
758+
return &metav1.DeleteOptions{Preconditions: &metav1.Preconditions{ResourceVersion: &badRV}}
759+
},
760+
expectErr: "The object might have been modified",
761+
},
762+
}
763+
764+
testContext := genericapirequest.WithNamespace(genericapirequest.NewContext(), metav1.NamespaceDefault)
765+
766+
storage, _, _, server := newStorage(t)
767+
defer server.Terminate(t)
768+
defer storage.Store.DestroyFunc()
769+
770+
client := fake.NewSimpleClientset()
771+
evictionRest := newEvictionStorage(storage.Store, client.PolicyV1())
772+
773+
for _, tc := range cases {
774+
for _, conditionsEnabled := range []bool{true, false} {
775+
name := fmt.Sprintf("%s_conditions=%v", tc.name, conditionsEnabled)
776+
t.Run(name, func(t *testing.T) {
777+
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.PodDisruptionConditions, conditionsEnabled)()
778+
var deleteOptions *metav1.DeleteOptions
779+
if tc.initialPod {
780+
newPod := validNewPod()
781+
createdObj, err := storage.Create(testContext, newPod, rest.ValidateAllObjectFunc, &metav1.CreateOptions{})
782+
if err != nil {
783+
t.Fatal(err)
784+
}
785+
t.Cleanup(func() {
786+
zero := int64(0)
787+
storage.Delete(testContext, newPod.Name, rest.ValidateAllObjectFunc, &metav1.DeleteOptions{GracePeriodSeconds: &zero})
788+
})
789+
deleteOptions = tc.makeDeleteOptions(createdObj.(*api.Pod))
790+
} else {
791+
deleteOptions = tc.makeDeleteOptions(nil)
792+
}
793+
if deleteOptions == nil {
794+
deleteOptions = &metav1.DeleteOptions{}
795+
}
796+
797+
err := addConditionAndDeletePod(evictionRest, testContext, "foo", rest.ValidateAllObjectFunc, deleteOptions)
798+
if err == nil {
799+
if tc.expectErr != "" {
800+
t.Fatalf("expected err containing %q, got none", tc.expectErr)
801+
}
802+
return
803+
}
804+
if tc.expectErr == "" {
805+
t.Fatalf("unexpected err: %v", err)
806+
}
807+
if !strings.Contains(err.Error(), tc.expectErr) {
808+
t.Fatalf("expected err containing %q, got %v", tc.expectErr, err)
809+
}
810+
})
811+
}
812+
}
813+
}
814+
712815
func resource(resource string) schema.GroupResource {
713816
return schema.GroupResource{Group: "", Resource: resource}
714817
}
@@ -765,7 +868,7 @@ func (ms *mockStore) Watch(ctx context.Context, options *metainternalversion.Lis
765868
}
766869

767870
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) {
768-
return nil, false, nil
871+
return ms.pod, false, nil
769872
}
770873

771874
func (ms *mockStore) Get(ctx context.Context, name string, options *metav1.GetOptions) (runtime.Object, error) {

0 commit comments

Comments
 (0)