Change:
What happened: a custom resource defintion a namespace a custom resource in the namespace with a finalizer attached the namespace custom resource in the namespace will get its deletionTimestamp field updated about every 8 seconds. What you expected to happen: The resource would have the deletionTimestamp set once then not be updated again. This becomes a hassle if a controller needs to perform an operation that takes longer than the update period as it will need to perform a read-modify-write with retry loop when removing the finalizer. How to reproduce it (as minimally and precisely as possible): Environment: Kubernetes version (use ):Cloud provider or hardware configuration: GKE 1.11
/sig api-machinery
/area custom-resources deletionTimestamps should not update even if subsequent deletion requests are made
/assign
as this description: if object has pending finalizers, the deletionTimestamps should be updated. Do we need to change this logic ?
deletionTimestamp is supposed to be immutable: storage modifying that timestamp is unexpected, at least to me
we should at least ensure that this documentation is honored:
opened to do that

Discussion:
} // markAsDeleting sets the obj's DeletionGracePeriodSeconds to 0, and sets the <del> // DeletionTimestamp to "now". Finalizers are watching for such updates and will </del> <ins> // DeletionTimestamp to "now" if there is no existing deletionTimestamp or if the existing // deletionTimestamp is further in future. Finalizers are watching for such updates and will </ins> // finalize the object if their IDs are present in the object's Finalizers list. <del> func markAsDeleting(obj runtime.Object) (err error) { </del> <ins> func markAsDeleting(obj runtime.Object, now time.Time) (err error) { </ins> objectMeta, kerr := meta.Accessor(obj) if kerr != nil { return kerr } <del> now := metav1.NewTime(time.Now()) </del> // This handles Generation bump for resources that don't support graceful // deletion. For resources that support graceful deletion is handle in // pkg/api/rest/delete.go if objectMeta.GetDeletionTimestamp() == nil && objectMeta.GetGeneration() > 0 { objectMeta.SetGeneration(objectMeta.GetGeneration() + 1) } <del> objectMeta.SetDeletionTimestamp(&now) </del> <ins> existingDeletionTimestamp := objectMeta.GetDeletionTimestamp() if existingDeletionTimestamp == nil || existingDeletionTimestamp.After(now) { metaNow := metav1.NewTime(now) objectMeta.SetDeletionTimestamp(&metaNow) } </ins> var zero int64 = 0 objectMeta.SetDeletionGracePeriodSeconds(&zero) return nil

// set the DeleteGracePeriods to 0 if the object has pendingFinalizers but not supporting graceful deletion if pendingFinalizers { klog.V(6).Infof("update the DeletionTimestamp to "now" and GracePeriodSeconds to 0 for object %s, because it has pending finalizers", name) <del> err = markAsDeleting(existing) </del> <ins> err = markAsDeleting(existing, time.Now()) </ins> if err != nil { return nil, err }

} } } <ins> func TestMarkAsDeleting(t *testing.T) { now := time.Now() soon := now.Add(time.Second) past := now.Add(-time.Second) newTimePointer := func(t time.Time) *metav1.Time { metaTime := metav1.NewTime(t) return &metaTime } testcases := []struct { name                    string deletionTimestamp       *metav1.Time expectDeletionTimestamp *metav1.Time }{ { name:                    "unset", deletionTimestamp:       nil, expectDeletionTimestamp: newTimePointer(now), }, { name:                    "set to future", deletionTimestamp:       newTimePointer(soon), expectDeletionTimestamp: newTimePointer(now), }, { name:                    "set to past", deletionTimestamp:       newTimePointer(past), expectDeletionTimestamp: newTimePointer(past), }, } for _, tc := range testcases { t.Run(tc.name, func(t *testing.T) { rs := &example.ReplicaSet{} rs.DeletionTimestamp = tc.deletionTimestamp if err := markAsDeleting(rs, now); err != nil { t.Error(err) } if reflect.DeepEqual(*rs.DeletionTimestamp, tc.expectDeletionTimestamp) { t.Errorf("expected %v, got %v", tc.expectDeletionTimestamp, *rs.DeletionTimestamp) } }) } } </ins>