Change:
<!-- Please use this template while reporting a bug and provide as much info as possible. Not doing so may result in your bug not being addressed in a timely manner. Thanks! If the matter is security related, please disclose it privately via --The api bind a pod to a node. But the pod is specified by name (not uid), which is not a unique identifier for pod. So the caller of can't make sure the pod is exactly what he want. api can bind a pod with specified uid to a node. If the uid not match, return an error a pending pod by statefulset named busybox, make sure it's pending, the scheduler will not bind it(use non-existed node selector or other method). So we will get a pending pod busybox-0. following code the in the code, delete the pod busybox-0 manually during the sleep or other method. The statefulset busybox will create another pod busybox-0 with different uid , the api in the code above will bind an unexpected pod to node The logic in scheduler is the same. If the pod is recreate with the same name and different resource require or selector between scheduler's and , scheduler will do a wrong bind for the pod. I think the root cause is the api does not check the uid. Kubernetes version (use ): Client Version: version.Info{Major:"1", Minor:"20+", GitVersion:"v1.20.0-alpha.3", GitCommit:"", GitTreeState:"clean", BuildDate:"2020-10-20T09:01:41Z", GoVersion:"go1.15.2", Compiler:"gc", Platform:"darwin/amd64"} Server Version: version.Info{Major:"1", Minor:"16+", GitVersion:"v1.16.9-aliyunedge.1", GitCommit:"", GitTreeState:"", BuildDate:"2021-03-08T09:56:01Z", GoVersion:"go1.13.9", Compiler:"gc", Platform:"linux/amd64"} Cloud provider or hardware configuration: OS (e.g: ): Kernel (e.g. ): Install tools: Network plugin and version (if this is a network-related bug): Others:
This issue is currently awaiting triage. If a SIG or subproject determines this is a relevant issue, they will accept it by applying the label and provide further guidance. The label can be by org members by writing in a comment. <detailsInstructions for interacting with me using PR comments are available . If you have questions or suggestions related to my behavior, please file an issue against the repository. </details
/sig api-machinery
/sig scheduling for triage (the binding feature of (pod to node) is for scheduling).
For the sake of precedence, are there other APIs that are based on object UID?
uids are used as a precondition is used in many places. if the server started checking that a uid on the binding object (if specified) matched the uid of the pod being modified, that seems sufficient
is there any way for the caller of Bind to confirm that he is binding the right pod? or not uid, use resource version?
that would require: the server to check that bind UID (if specified) matches the pod UID (passing the binding uid from BindingREST#Create → assignPod → setPodHostAndAnnotations and verifying the uid match in setPodHostAndAnnotations) the binding API caller to set in the posted binding object (as illustrated in the description of this issue)
actually, kube-scheduler(the most common binding API caller) has set the in the posted binding object. but the server doesn't check the uid. in my program, i do those to work around(not elegant, but it works...): call the binding API with an uid in annotation(i found that BindingREST#Create → assignPod → setPodHostAndAnnotations will carry annotation). check the matchs , if not match, delete the pod and wait for an new pod ... from your point of view, is it necessary to ensure the pod is the caller want when someone call bind API? what's your advice for this issue
given the scheduler made decisions based on the current shape of the pod spec, I think it makes sense for it to work to make sure it is binding that instance of the pod I would advocate for supporting uid as a precondition in the binding API and starting to use it from the scheduler
i agree with you
/remove-sig api-machinery
I also agree with this and this cannot achieve only on sig/scheduling. Sorry, if it is not suitable, but I'd like to add sig/api-machinery label again. /sig api-machinery
Either scheduling or node sigs own the pod binding API. Either of those would be a good place to coordinate adding a fix to honor uid as a precondition API machinery owns the generic API plumbing and API server behavior, not implementations of most specific APIs like the pod or pod binding APIs /remove-sig api-machinery
Given you already exercised Jodan's suggestion locally, would you mind raising an offical PR?
how about this PR

Discussion:
"k8s.io/apimachinery/pkg/api/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" <ins> "k8s.io/apimachinery/pkg/types" </ins> "k8s.io/apiserver/pkg/registry/generic" genericregistry "k8s.io/apiserver/pkg/registry/generic/registry" "k8s.io/apiserver/pkg/registry/rest"

} } <del> err = r.assignPod(ctx, binding.Name, binding.Target.Name, binding.Annotations, dryrun.IsDryRun(options.DryRun)) </del> <ins> err = r.assignPod(ctx, binding.UID, binding.ResourceVersion, binding.Name, binding.Target.Name, binding.Annotations, dryrun.IsDryRun(options.DryRun)) </ins> out = &metav1.Status{Status: metav1.StatusSuccess} return }

// setPodHostAndAnnotations sets the given pod's host to 'machine' if and only if it was // previously 'oldMachine' and merges the provided annotations with those of the pod. // Returns the current state of the pod, or an error. <del> func (r *BindingREST) setPodHostAndAnnotations(ctx context.Context, podID, oldMachine, machine string, annotations map[string]string, dryRun bool) (finalPod *api.Pod, err error) { </del> <ins> func (r *BindingREST) setPodHostAndAnnotations(ctx context.Context, podUID types.UID, podResourceVersion, podID, oldMachine, machine string, annotations map[string]string, dryRun bool) (finalPod *api.Pod, err error) { </ins> podKey, err := r.store.KeyFunc(ctx, podID) if err != nil { return nil, err } <del> err = r.store.Storage.GuaranteedUpdate(ctx, podKey, &api.Pod{}, false, nil, storage.SimpleUpdate(func(obj runtime.Object) (runtime.Object, error) { </del> <ins> var preconditions *storage.Preconditions if podUID != "" || podResourceVersion != "" { preconditions = &storage.Preconditions{} if podUID != "" { preconditions.UID = &podUID } if podResourceVersion != "" { preconditions.ResourceVersion = &podResourceVersion } } err = r.store.Storage.GuaranteedUpdate(ctx, podKey, &api.Pod{}, false, preconditions, storage.SimpleUpdate(func(obj runtime.Object) (runtime.Object, error) { </ins> pod, ok := obj.(*api.Pod) if !ok { return nil, fmt.Errorf("unexpected object: %#v", obj)

} // assignPod assigns the given pod to the given machine. <del> func (r *BindingREST) assignPod(ctx context.Context, podID string, machine string, annotations map[string]string, dryRun bool) (err error) { if _, err = r.setPodHostAndAnnotations(ctx, podID, "", machine, annotations, dryRun); err != nil { </del> <ins> func (r *BindingREST) assignPod(ctx context.Context, podUID types.UID, podResourceVersion, podID string, machine string, annotations map[string]string, dryRun bool) (err error) { if _, err = r.setPodHostAndAnnotations(ctx, podUID, podResourceVersion, podID, "", machine, annotations, dryRun); err != nil { </ins> err = storeerr.InterpretGetError(err, api.Resource("pods"), podID) err = storeerr.InterpretUpdateError(err, api.Resource("pods"), podID) if _, ok := err.(*errors.StatusError); !ok {

"k8s.io/apimachinery/pkg/fields" "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/runtime" <ins> "k8s.io/apimachinery/pkg/types" </ins> "k8s.io/apimachinery/pkg/util/diff" genericapirequest "k8s.io/apiserver/pkg/endpoints/request" "k8s.io/apiserver/pkg/registry/generic"

} } <ins> func validNewBinding() *api.Binding { return &api.Binding{ ObjectMeta: metav1.ObjectMeta{Name: "foo"}, Target:     api.ObjectReference{Name: "machine", Kind: "Node"}, } } func TestEtcdCreateBindingWithUIDAndResourceVersion(t *testing.T) { originUID := func(pod *api.Pod) types.UID { return pod.UID } emptyUID := func(pod *api.Pod) types.UID { return "" } changedUID := func(pod *api.Pod) types.UID { return pod.UID + "-changed" } originResourceVersion := func(pod *api.Pod) string { return pod.ResourceVersion } emptyResourceVersion := func(pod *api.Pod) string { return "" } changedResourceVersion := func(pod *api.Pod) string { return pod.ResourceVersion + "-changed" } noError := func(err error) bool { return err == nil } conflictError := func(err error) bool { return err != nil && errors.IsConflict(err) } testCases := map[string]struct { podUIDGetter             func(pod *api.Pod) types.UID podResourceVersionGetter func(pod *api.Pod) string errOK                    func(error) bool expectedNodeName         string }{ "originUID-originResourceVersion": { podUIDGetter:             originUID, podResourceVersionGetter: originResourceVersion, errOK:                    noError, expectedNodeName:         "machine", }, "originUID-emptyResourceVersion": { podUIDGetter:             originUID, podResourceVersionGetter: emptyResourceVersion, errOK:                    noError, expectedNodeName:         "machine", }, "originUID-changedResourceVersion": { podUIDGetter:             originUID, podResourceVersionGetter: changedResourceVersion, errOK:                    conflictError, expectedNodeName:         "", }, "emptyUID-originResourceVersion": { podUIDGetter:             emptyUID, podResourceVersionGetter: originResourceVersion, errOK:                    noError, expectedNodeName:         "machine", }, "emptyUID-emptyResourceVersion": { podUIDGetter:             emptyUID, podResourceVersionGetter: emptyResourceVersion, errOK:                    noError, expectedNodeName:         "machine", }, "emptyUID-changedResourceVersion": { podUIDGetter:             emptyUID, podResourceVersionGetter: changedResourceVersion, errOK:                    conflictError, expectedNodeName:         "", }, "changedUID-originResourceVersion": { podUIDGetter:             changedUID, podResourceVersionGetter: originResourceVersion, errOK:                    conflictError, expectedNodeName:         "", }, "changedUID-emptyResourceVersion": { podUIDGetter:             changedUID, podResourceVersionGetter: emptyResourceVersion, errOK:                    conflictError, expectedNodeName:         "", }, "changedUID-changedResourceVersion": { podUIDGetter:             changedUID, podResourceVersionGetter: changedResourceVersion, errOK:                    conflictError, expectedNodeName:         "", }, } storage, bindingStorage, _, server := newStorage(t) defer server.Terminate(t) defer storage.Store.DestroyFunc() for k, testCase := range testCases { pod := validNewPod() pod.Namespace = fmt.Sprintf("namespace-%s", strings.ToLower(k)) ctx := genericapirequest.WithNamespace(genericapirequest.NewDefaultContext(), pod.Namespace) podCreated, err := storage.Create(ctx, pod, rest.ValidateAllObjectFunc, &metav1.CreateOptions{}) if err != nil { t.Fatalf("%s: unexpected error: %v", k, err) } binding := validNewBinding() binding.UID = testCase.podUIDGetter(podCreated.(*api.Pod)) binding.ResourceVersion = testCase.podResourceVersionGetter(podCreated.(*api.Pod)) if _, err := bindingStorage.Create(ctx, binding.Name, binding, rest.ValidateAllObjectFunc, &metav1.CreateOptions{}); !testCase.errOK(err) { t.Errorf("%s: unexpected error: %v", k, err) } if pod, err := storage.Get(ctx, pod.Name, &metav1.GetOptions{}); err != nil { t.Errorf("%s: unexpected error: %v", k, err) } else if pod.(*api.Pod).Spec.NodeName != testCase.expectedNodeName { t.Errorf("%s: expected: %v, got: %v", k, pod.(*api.Pod).Spec.NodeName, testCase.expectedNodeName) } } } </ins> func TestEtcdCreateWithExistingContainers(t *testing.T) { storage, bindingStorage, _, server := newStorage(t) defer server.Terminate(t)