Change:
Recap of related tangent in follows. Use case: I need to implement mesos task reconciliation in the kubernetes-mesos scheduler. If it crashes and then restarts, I want to be able to recover things like taskID, slaveID, offerID, etc. so as to rebuild the internal state of the scheduler. Once state is recovered, I can reconcile the against the tasks that the mesos master knows about. If I can't use binding annotations for this, then I need to store state some other (super inconvenient) way. Currently the etcd registry assignPod() implementation drops binding annotations on the floor. It should not, and instead they should be preserved for later access. Proposal: Store binding-related annotations in a new field: Pod.Status.Annotations When setting Pod.Status.Host="{some-host-name}" via assignPod(), binding annotations are copied in from Binding When cloning Pod.Status.Host in UpdatePod() (for an already-scheduled pod), the Pod.Status.Annotations are also cloned. I don't think /bindings has to be a readable apiserver endpoint, as long as the binding metadata is exposed some other way, as proposed above. I'd also like to propose that once binding annotations are "bound" to Pod.Status they become read-only. So they're essentially write-once, just like Pod.Status.Host is meant to be. cc xref
There is one situation where we probably want to clear Host, actually: in the case that the pod can't actually be materialized on the selected node for some reason, such as .
Any objection to a PR implementing my proposal? I'm happy to do the work.
You'll probably want to do it on top of (or after it lands) since that code path is changing significantly.
Though it is also represented in PodStatus, Host is properly a field in the PodSpec. Status must be completely reconstructable based on observations. The proposed Pod.Status.Annotations would not be. Furthermore, I don't think we should introduce another annotations field to Pod. I'd prefer to add a podAnnotations field to Binding, and then merge them into the pod. As for clearing the annotations, I'd prefer to simply fail a pod that was rejected by a node so as not to have to deal with that. Also note the discussion here re. moving binding to a pod subresource:
See also .

Discussion:
if len(binding.Target.Name) == 0 { return nil, errors.NewInvalid("binding", binding.Name, errors.ValidationErrorList{errors.NewFieldRequired("to.name", binding.Target.Name)}) } <del> err = r.assignPod(ctx, binding.Name, binding.Target.Name) err = etcderr.InterpretCreateError(err, "binding", "") </del> <ins> err = r.assignPod(ctx, binding.Name, binding.Target.Name, binding.Annotations) </ins> out = &api.Status{Status: api.StatusSuccess} return } <del> // setPodHostTo sets the given pod's host to 'machine' iff it was previously 'oldMachine'. </del> <ins> // setPodHostAndAnnotations sets the given pod's host to 'machine' iff it was previously 'oldMachine' and merges // the provided annotations with those of the pod. </ins> // Returns the current state of the pod, or an error. <del> func (r *BindingREST) setPodHostTo(ctx api.Context, podID, oldMachine, machine string) (finalPod *api.Pod, err error) { </del> <ins> func (r *BindingREST) setPodHostAndAnnotations(ctx api.Context, podID, oldMachine, machine string, annotations map[string]string) (finalPod *api.Pod, err error) { </ins> podKey, err := r.store.KeyFunc(ctx, podID) if err != nil { return nil, err

} pod.Spec.Host = machine pod.Status.Host = machine <ins> if pod.Annotations == nil { pod.Annotations = make(map[string]string) } for k, v := range annotations { pod.Annotations[k] = v } </ins> finalPod = pod return pod, nil })

} // assignPod assigns the given pod to the given machine. <del> func (r *BindingREST) assignPod(ctx api.Context, podID string, machine string) error { _, err := r.setPodHostTo(ctx, podID, "", machine) if err != nil { return err </del> <ins> func (r *BindingREST) assignPod(ctx api.Context, podID string, machine string, annotations map[string]string) (err error) { if _, err = r.setPodHostAndAnnotations(ctx, podID, "", machine, annotations); err != nil { err = etcderr.InterpretGetError(err, "pod", podID) err = etcderr.InterpretUpdateError(err, "pod", podID) if _, ok := err.(*errors.StatusError); !ok { err = errors.NewConflict("binding", podID, err) } </ins> } <del> return err </del> <ins> return </ins> } type podLifecycle struct{}

// Suddenly, a wild scheduler appears: _, err = bindingRegistry.Create(ctx, &api.Binding{ <del> ObjectMeta: api.ObjectMeta{Namespace: api.NamespaceDefault, Name: "foo"}, Target:     api.ObjectReference{Name: "machine"}, </del> <ins> ObjectMeta: api.ObjectMeta{ Namespace:   api.NamespaceDefault, Name:        "foo", Annotations: map[string]string{"label1": "value1"}, }, Target: api.ObjectReference{Name: "machine"}, </ins> }) if err != nil { t.Fatalf("unexpected error: %v", err)

if pod.Name != "foo" { t.Errorf("Unexpected pod: %#v %s", pod, resp.Node.Value) } <ins> if !(pod.Annotations != nil && pod.Annotations["label1"] == "value1") { t.Fatalf("Pod annotations don't match the expected: %v", pod.Annotations) } } func TestEtcdCreateWithConflict(t *testing.T) { registry, bindingRegistry, _, fakeClient, _ := newStorage(t) ctx := api.NewDefaultContext() fakeClient.TestIndex = true key, _ := registry.store.KeyFunc(ctx, "foo") fakeClient.Data[key] = tools.EtcdResponseWithError{ R: &etcd.Response{ Node: nil, }, E: tools.EtcdErrorNotFound, } _, err := registry.Create(ctx, validNewPod()) if err != nil { t.Fatalf("unexpected error: %v", err) } // Suddenly, a wild scheduler appears: binding := api.Binding{ ObjectMeta: api.ObjectMeta{ Namespace:   api.NamespaceDefault, Name:        "foo", Annotations: map[string]string{"label1": "value1"}, }, Target: api.ObjectReference{Name: "machine"}, } _, err = bindingRegistry.Create(ctx, &binding) if err != nil { t.Fatalf("unexpected error: %v", err) } _, err = bindingRegistry.Create(ctx, &binding) if err == nil || !errors.IsConflict(err) { t.Fatalf("expected resource conflict error, not: %v", err) } </ins> } func TestEtcdCreateWithExistingContainers(t *testing.T) {