diff --git a/pkg/registry/core/pod/strategy.go b/pkg/registry/core/pod/strategy.go index f9905f1a4cb..505e2c50bdd 100644 --- a/pkg/registry/core/pod/strategy.go +++ b/pkg/registry/core/pod/strategy.go @@ -227,9 +227,38 @@ func (podStatusStrategy) PrepareForUpdate(ctx context.Context, obj, old runtime. newPod.Status.QOSClass = oldPod.Status.QOSClass } + preserveOldObservedGeneration(newPod, oldPod) podutil.DropDisabledPodFields(newPod, oldPod) } +// If a client request tries to clear `observedGeneration`, in the pod status or +// conditions, we preserve the original value. +func preserveOldObservedGeneration(newPod, oldPod *api.Pod) { + if newPod.Status.ObservedGeneration == 0 { + newPod.Status.ObservedGeneration = oldPod.Status.ObservedGeneration + } + + // Remember observedGeneration values from old status conditions. + // This is a list per type because validation permits multiple conditions with the same type. + oldConditionGenerations := map[api.PodConditionType][]int64{} + for _, oldCondition := range oldPod.Status.Conditions { + oldConditionGenerations[oldCondition.Type] = append(oldConditionGenerations[oldCondition.Type], oldCondition.ObservedGeneration) + } + + // For any conditions in the new status without observedGeneration set, preserve the old value. + for i, newCondition := range newPod.Status.Conditions { + oldGeneration := int64(0) + if oldGenerations, ok := oldConditionGenerations[newCondition.Type]; ok && len(oldGenerations) > 0 { + oldGeneration = oldGenerations[0] + oldConditionGenerations[newCondition.Type] = oldGenerations[1:] + } + + if newCondition.ObservedGeneration == 0 { + newPod.Status.Conditions[i].ObservedGeneration = oldGeneration + } + } +} + func (podStatusStrategy) ValidateUpdate(ctx context.Context, obj, old runtime.Object) field.ErrorList { pod := obj.(*api.Pod) oldPod := old.(*api.Pod) diff --git a/pkg/registry/core/pod/strategy_test.go b/pkg/registry/core/pod/strategy_test.go index 2b3fd197040..d0aefefb51d 100644 --- a/pkg/registry/core/pod/strategy_test.go +++ b/pkg/registry/core/pod/strategy_test.go @@ -3396,6 +3396,161 @@ func TestStatusPrepareForUpdate(t *testing.T) { }, }, }, + { + description: "preserve old status.observedGeneration if empty", + oldPod: &api.Pod{ + ObjectMeta: metav1.ObjectMeta{Name: "pod"}, + Status: api.PodStatus{ + ObservedGeneration: 20, + }, + }, + newPod: &api.Pod{ + ObjectMeta: metav1.ObjectMeta{Name: "pod"}, + }, + expected: &api.Pod{ + ObjectMeta: metav1.ObjectMeta{Name: "pod"}, + Status: api.PodStatus{ + ObservedGeneration: 20, + }, + }, + }, + { + description: "preserve old conditions.observedGeneration if empty", + oldPod: &api.Pod{ + ObjectMeta: metav1.ObjectMeta{Name: "pod"}, + Status: api.PodStatus{ + Conditions: []api.PodCondition{ + { + Type: "old=without,new=without", + Status: api.ConditionTrue, + }, + { + Type: "old=with,new=without", + Status: api.ConditionTrue, + ObservedGeneration: 20, + }, + { + Type: "old=without,new=with", + Status: api.ConditionTrue, + }, + { + Type: "old=with,new=with", + Status: api.ConditionTrue, + ObservedGeneration: 20, + }, + { + Type: "removed-condition", + Status: api.ConditionTrue, + ObservedGeneration: 1, + }, + { + Type: "duplicate type", + Status: api.ConditionTrue, + }, + { + Type: "duplicate type", + Status: api.ConditionTrue, + ObservedGeneration: 1, + }, + { + Type: "duplicate type", + Status: api.ConditionTrue, + ObservedGeneration: 2, + }, + }, + }, + }, + newPod: &api.Pod{ + ObjectMeta: metav1.ObjectMeta{Name: "pod"}, + Status: api.PodStatus{ + Conditions: []api.PodCondition{ + { + Type: "old=without,new=without", + Status: api.ConditionTrue, + }, + { + Type: "old=with,new=without", + Status: api.ConditionTrue, + }, + { + Type: "old=without,new=with", + Status: api.ConditionTrue, + ObservedGeneration: 20, + }, + { + Type: "old=with,new=with", + Status: api.ConditionTrue, + ObservedGeneration: 20, + }, + { + Type: "new-condition", + Status: api.ConditionTrue, + ObservedGeneration: 1, + }, + { + Type: "duplicate type", + Status: api.ConditionTrue, + ObservedGeneration: 1, + }, + { + Type: "duplicate type", + Status: api.ConditionTrue, + }, + { + Type: "duplicate type", + Status: api.ConditionTrue, + ObservedGeneration: 2, + }, + }, + }, + }, + expected: &api.Pod{ + ObjectMeta: metav1.ObjectMeta{Name: "pod"}, + Status: api.PodStatus{ + Conditions: []api.PodCondition{ + { + Type: "old=without,new=without", + Status: api.ConditionTrue, + }, + { + Type: "old=with,new=without", + Status: api.ConditionTrue, + ObservedGeneration: 20, + }, + { + Type: "old=without,new=with", + Status: api.ConditionTrue, + ObservedGeneration: 20, + }, + { + Type: "old=with,new=with", + Status: api.ConditionTrue, + ObservedGeneration: 20, + }, + { + Type: "new-condition", + Status: api.ConditionTrue, + ObservedGeneration: 1, + }, + { + Type: "duplicate type", + Status: api.ConditionTrue, + ObservedGeneration: 1, + }, + { + Type: "duplicate type", + Status: api.ConditionTrue, + ObservedGeneration: 1, + }, + { + Type: "duplicate type", + Status: api.ConditionTrue, + ObservedGeneration: 2, + }, + }, + }, + }, + }, } for _, tc := range testCases {