Skip to content

Commit 2ca9e2d

Browse files
authored
Merge pull request kubernetes#122646 from liggitt/deletionTimestamp
prevent deletionTimestamp from moving into the past
2 parents f0077a3 + 88090c4 commit 2ca9e2d

File tree

3 files changed

+68
-5
lines changed

3 files changed

+68
-5
lines changed

staging/src/k8s.io/apiserver/pkg/registry/rest/delete.go

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -126,9 +126,22 @@ func BeforeDelete(strategy RESTDeleteStrategy, ctx context.Context, obj runtime.
126126
if period >= *objectMeta.GetDeletionGracePeriodSeconds() {
127127
return false, true, nil
128128
}
129+
// Move the existing deletionTimestamp back by existing object.DeletionGracePeriod, then forward by options.DeletionGracePeriod.
130+
// This moves the deletionTimestamp back, since the grace period can only be shortened in this code path.
129131
newDeletionTimestamp := metav1.NewTime(
130132
objectMeta.GetDeletionTimestamp().Add(-time.Second * time.Duration(*objectMeta.GetDeletionGracePeriodSeconds())).
131133
Add(time.Second * time.Duration(*options.GracePeriodSeconds)))
134+
// Prevent shortening the grace period moving deletionTimestamp into the past
135+
if now := metav1Now(); newDeletionTimestamp.Before(&now) {
136+
newDeletionTimestamp = now
137+
if period != 0 {
138+
// Since a graceful deletion was requested (options.GracePeriodSeconds != 0), but the entire grace period has already expired,
139+
// shorten to the minimum period possible while still treating this as a graceful delete.
140+
// This means the API server updates the object, another actor observes the update
141+
// and is still responsible for the final delete with options.GracePeriodSeconds == 0.
142+
period = int64(1)
143+
}
144+
}
132145
objectMeta.SetDeletionTimestamp(&newDeletionTimestamp)
133146
objectMeta.SetDeletionGracePeriodSeconds(&period)
134147
return true, false, nil
@@ -147,8 +160,8 @@ func BeforeDelete(strategy RESTDeleteStrategy, ctx context.Context, obj runtime.
147160
return false, false, errors.NewInternalError(fmt.Errorf("options.GracePeriodSeconds should not be nil"))
148161
}
149162

150-
now := metav1.NewTime(metav1.Now().Add(time.Second * time.Duration(*options.GracePeriodSeconds)))
151-
objectMeta.SetDeletionTimestamp(&now)
163+
requestedDeletionTimestamp := metav1.NewTime(metav1Now().Add(time.Second * time.Duration(*options.GracePeriodSeconds)))
164+
objectMeta.SetDeletionTimestamp(&requestedDeletionTimestamp)
152165
objectMeta.SetDeletionGracePeriodSeconds(options.GracePeriodSeconds)
153166
// If it's the first graceful deletion we are going to set the DeletionTimestamp to non-nil.
154167
// Controllers of the object that's being deleted shouldn't take any nontrivial actions, hence its behavior changes.

staging/src/k8s.io/apiserver/pkg/registry/rest/delete_test.go

Lines changed: 49 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,9 @@ package rest
1818

1919
import (
2020
"context"
21+
"reflect"
2122
"testing"
23+
"time"
2224

2325
v1 "k8s.io/api/core/v1"
2426
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -52,15 +54,32 @@ func TestBeforeDelete(t *testing.T) {
5254
options *metav1.DeleteOptions
5355
}
5456

55-
makePod := func(deletionGracePeriodSeconds int64) *v1.Pod {
57+
// snapshot and restore real metav1Now function
58+
originalMetav1Now := metav1Now
59+
t.Cleanup(func() {
60+
metav1Now = originalMetav1Now
61+
})
62+
63+
// make now refer to a fixed point in time
64+
now := metav1.Time{Time: time.Now().Truncate(time.Second)}
65+
metav1Now = func() metav1.Time {
66+
return now
67+
}
68+
69+
makePodWithDeletionTimestamp := func(deletionTimestamp *metav1.Time, deletionGracePeriodSeconds int64) *v1.Pod {
5670
return &v1.Pod{
5771
TypeMeta: metav1.TypeMeta{APIVersion: "v1", Kind: "Pod"},
5872
ObjectMeta: metav1.ObjectMeta{
59-
DeletionTimestamp: &metav1.Time{},
73+
DeletionTimestamp: deletionTimestamp,
6074
DeletionGracePeriodSeconds: &deletionGracePeriodSeconds,
6175
},
6276
}
6377
}
78+
makePod := func(deletionGracePeriodSeconds int64) *v1.Pod {
79+
deletionTimestamp := now
80+
deletionTimestamp.Time = deletionTimestamp.Time.Add(time.Duration(deletionGracePeriodSeconds) * time.Second)
81+
return makePodWithDeletionTimestamp(&deletionTimestamp, deletionGracePeriodSeconds)
82+
}
6483
makeOption := func(gracePeriodSeconds int64) *metav1.DeleteOptions {
6584
return &metav1.DeleteOptions{
6685
GracePeriodSeconds: &gracePeriodSeconds,
@@ -74,6 +93,7 @@ func TestBeforeDelete(t *testing.T) {
7493
wantGracefulPending bool
7594
wantGracePeriodSeconds *int64
7695
wantDeletionGracePeriodSeconds *int64
96+
wantDeletionTimestamp *metav1.Time
7797
wantErr bool
7898
}{
7999
{
@@ -271,6 +291,28 @@ func TestBeforeDelete(t *testing.T) {
271291
wantGraceful: false,
272292
wantGracefulPending: true,
273293
},
294+
{
295+
name: "when a shorter non-zero grace period would move into the past",
296+
args: args{
297+
pod: makePodWithDeletionTimestamp(&metav1.Time{Time: now.Time.Add(-time.Minute)}, 60),
298+
options: makeOption(50),
299+
},
300+
wantDeletionTimestamp: &now,
301+
wantDeletionGracePeriodSeconds: utilpointer.Int64(1),
302+
wantGracePeriodSeconds: utilpointer.Int64(50),
303+
wantGraceful: true,
304+
},
305+
{
306+
name: "when a zero grace period would move into the past",
307+
args: args{
308+
pod: makePodWithDeletionTimestamp(&metav1.Time{Time: now.Time.Add(-time.Minute)}, 60),
309+
options: makeOption(0),
310+
},
311+
wantDeletionTimestamp: &now,
312+
wantDeletionGracePeriodSeconds: utilpointer.Int64(0),
313+
wantGracePeriodSeconds: utilpointer.Int64(0),
314+
wantGraceful: true,
315+
},
274316
}
275317
for _, tt := range tests {
276318
t.Run(tt.name, func(t *testing.T) {
@@ -301,6 +343,11 @@ func TestBeforeDelete(t *testing.T) {
301343
if !utilpointer.Int64Equal(tt.args.options.GracePeriodSeconds, tt.wantGracePeriodSeconds) {
302344
t.Errorf("options.GracePeriodSeconds = %v, want %v", ptr.Deref(tt.args.options.GracePeriodSeconds, 0), ptr.Deref(tt.wantGracePeriodSeconds, 0))
303345
}
346+
if tt.wantDeletionTimestamp != nil {
347+
if !reflect.DeepEqual(tt.args.pod.DeletionTimestamp, tt.wantDeletionTimestamp) {
348+
t.Errorf("pod.deletionTimestamp = %v, want %v", tt.args.pod.DeletionTimestamp, tt.wantDeletionTimestamp)
349+
}
350+
}
304351
})
305352
}
306353
}

staging/src/k8s.io/apiserver/pkg/registry/rest/meta.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@ import (
2323
"k8s.io/apimachinery/pkg/util/uuid"
2424
)
2525

26+
// metav1Now returns metav1.Now(), but allows override for unit testing
27+
var metav1Now = func() metav1.Time { return metav1.Now() }
28+
2629
// WipeObjectMetaSystemFields erases fields that are managed by the system on ObjectMeta.
2730
func WipeObjectMetaSystemFields(meta metav1.Object) {
2831
meta.SetCreationTimestamp(metav1.Time{})
@@ -34,7 +37,7 @@ func WipeObjectMetaSystemFields(meta metav1.Object) {
3437

3538
// FillObjectMetaSystemFields populates fields that are managed by the system on ObjectMeta.
3639
func FillObjectMetaSystemFields(meta metav1.Object) {
37-
meta.SetCreationTimestamp(metav1.Now())
40+
meta.SetCreationTimestamp(metav1Now())
3841
meta.SetUID(uuid.NewUUID())
3942
}
4043

0 commit comments

Comments
 (0)