Skip to content

Commit

Permalink
Merge pull request #98866 from wzshiming/fix/termination_grace_period…
Browse files Browse the repository at this point in the history
…_seconds_is_negative

Fix TerminationGracePeriodSeconds is negative (part 1)
  • Loading branch information
k8s-ci-robot committed Jun 28, 2021
2 parents 6010cbe + a8d4cfa commit 5e06f17
Show file tree
Hide file tree
Showing 6 changed files with 417 additions and 22 deletions.
9 changes: 8 additions & 1 deletion pkg/apis/core/validation/validation.go
Original file line number Diff line number Diff line change
Expand Up @@ -3973,6 +3973,7 @@ func ValidatePodUpdate(newPod, oldPod *core.Pod, opts PodValidationOptions) fiel
// 1. spec.containers[*].image
// 2. spec.initContainers[*].image
// 3. spec.activeDeadlineSeconds
// 4. spec.terminationGracePeriodSeconds

containerErrs, stop := ValidateContainerUpdates(newPod.Spec.Containers, oldPod.Spec.Containers, specPath.Child("containers"))
allErrs = append(allErrs, containerErrs...)
Expand Down Expand Up @@ -4039,11 +4040,17 @@ func ValidatePodUpdate(newPod, oldPod *core.Pod, opts PodValidationOptions) fiel
// tolerations are checked before the deep copy, so munge those too
mungedPodSpec.Tolerations = oldPod.Spec.Tolerations // +k8s:verify-mutation:reason=clone

// Relax validation of immutable fields to allow it to be set to 1 if it was previously negative.
if oldPod.Spec.TerminationGracePeriodSeconds != nil && *oldPod.Spec.TerminationGracePeriodSeconds < 0 &&
mungedPodSpec.TerminationGracePeriodSeconds != nil && *mungedPodSpec.TerminationGracePeriodSeconds == 1 {
mungedPodSpec.TerminationGracePeriodSeconds = oldPod.Spec.TerminationGracePeriodSeconds // +k8s:verify-mutation:reason=clone
}

if !apiequality.Semantic.DeepEqual(mungedPodSpec, oldPod.Spec) {
// This diff isn't perfect, but it's a helluva lot better an "I'm not going to tell you what the difference is".
//TODO: Pinpoint the specific field that causes the invalid error after we have strategic merge diff
specDiff := diff.ObjectDiff(mungedPodSpec, oldPod.Spec)
allErrs = append(allErrs, field.Forbidden(specPath, fmt.Sprintf("pod updates may not change fields other than `spec.containers[*].image`, `spec.initContainers[*].image`, `spec.activeDeadlineSeconds` or `spec.tolerations` (only additions to existing tolerations)\n%v", specDiff)))
allErrs = append(allErrs, field.Forbidden(specPath, fmt.Sprintf("pod updates may not change fields other than `spec.containers[*].image`, `spec.initContainers[*].image`, `spec.activeDeadlineSeconds`, `spec.tolerations` (only additions to existing tolerations) or `spec.terminationGracePeriodSeconds` (allow it to be set to 1 if it was previously negative)\n%v", specDiff)))
}

return allErrs
Expand Down
40 changes: 40 additions & 0 deletions pkg/apis/core/validation/validation_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9803,6 +9803,46 @@ func TestValidatePodUpdate(t *testing.T) {
"spec: Forbidden: pod updates",
"removed priority class name",
},
{
core.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "foo",
},
Spec: core.PodSpec{
TerminationGracePeriodSeconds: utilpointer.Int64Ptr(1),
},
},
core.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "foo",
},
Spec: core.PodSpec{
TerminationGracePeriodSeconds: utilpointer.Int64Ptr(-1),
},
},
"",
"update termination grace period seconds",
},
{
core.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "foo",
},
Spec: core.PodSpec{
TerminationGracePeriodSeconds: utilpointer.Int64Ptr(0),
},
},
core.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "foo",
},
Spec: core.PodSpec{
TerminationGracePeriodSeconds: utilpointer.Int64Ptr(-1),
},
},
"spec: Forbidden: pod updates",
"update termination grace period seconds not 1",
},
}

for _, test := range tests {
Expand Down
5 changes: 5 additions & 0 deletions pkg/registry/core/pod/strategy.go
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,11 @@ func (podStrategy) CheckGracefulDelete(ctx context.Context, obj runtime.Object,
if pod.Status.Phase == api.PodFailed || pod.Status.Phase == api.PodSucceeded {
period = 0
}

if period < 0 {
period = 1
}

// ensure the options and the pod are in sync
options.GracePeriodSeconds = &period
return true
Expand Down
70 changes: 49 additions & 21 deletions pkg/registry/core/pod/strategy_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ import (
api "k8s.io/kubernetes/pkg/apis/core"
"k8s.io/kubernetes/pkg/features"
"k8s.io/kubernetes/pkg/kubelet/client"
utilpointer "k8s.io/utils/pointer"

// ensure types are installed
_ "k8s.io/kubernetes/pkg/apis/core/install"
Expand Down Expand Up @@ -266,55 +267,82 @@ func TestGetPodQOS(t *testing.T) {
func TestCheckGracefulDelete(t *testing.T) {
defaultGracePeriod := int64(30)
tcs := []struct {
in *api.Pod
gracePeriod int64
name string
pod *api.Pod
deleteGracePeriod *int64
gracePeriod int64
}{
{
in: &api.Pod{
name: "in pending phase with has node name",
pod: &api.Pod{
Spec: api.PodSpec{NodeName: "something"},
Status: api.PodStatus{Phase: api.PodPending},
},

gracePeriod: defaultGracePeriod,
deleteGracePeriod: &defaultGracePeriod,
gracePeriod: defaultGracePeriod,
},
{
in: &api.Pod{
name: "in failed phase with has node name",
pod: &api.Pod{
Spec: api.PodSpec{NodeName: "something"},
Status: api.PodStatus{Phase: api.PodFailed},
},
gracePeriod: 0,
deleteGracePeriod: &defaultGracePeriod,
gracePeriod: 0,
},
{
in: &api.Pod{
name: "in failed phase",
pod: &api.Pod{
Spec: api.PodSpec{},
Status: api.PodStatus{Phase: api.PodPending},
},
gracePeriod: 0,
deleteGracePeriod: &defaultGracePeriod,
gracePeriod: 0,
},
{
in: &api.Pod{
name: "in succeeded phase",
pod: &api.Pod{
Spec: api.PodSpec{},
Status: api.PodStatus{Phase: api.PodSucceeded},
},
gracePeriod: 0,
deleteGracePeriod: &defaultGracePeriod,
gracePeriod: 0,
},
{
in: &api.Pod{
name: "no phase",
pod: &api.Pod{
Spec: api.PodSpec{},
Status: api.PodStatus{},
},
gracePeriod: 0,
deleteGracePeriod: &defaultGracePeriod,
gracePeriod: 0,
},
{
name: "has negative grace period",
pod: &api.Pod{
Spec: api.PodSpec{
NodeName: "something",
TerminationGracePeriodSeconds: utilpointer.Int64(-1),
},
Status: api.PodStatus{},
},
gracePeriod: 1,
},
}
for _, tc := range tcs {
out := &metav1.DeleteOptions{GracePeriodSeconds: &defaultGracePeriod}
Strategy.CheckGracefulDelete(genericapirequest.NewContext(), tc.in, out)
if out.GracePeriodSeconds == nil {
t.Errorf("out grace period was nil but supposed to be %v", tc.gracePeriod)
}
if *(out.GracePeriodSeconds) != tc.gracePeriod {
t.Errorf("out grace period was %v but was expected to be %v", *out, tc.gracePeriod)
}
t.Run(tc.name, func(t *testing.T) {
out := &metav1.DeleteOptions{}
if tc.deleteGracePeriod != nil {
out.GracePeriodSeconds = utilpointer.Int64(*tc.deleteGracePeriod)
}
Strategy.CheckGracefulDelete(genericapirequest.NewContext(), tc.pod, out)
if out.GracePeriodSeconds == nil {
t.Errorf("out grace period was nil but supposed to be %v", tc.gracePeriod)
}
if *(out.GracePeriodSeconds) != tc.gracePeriod {
t.Errorf("out grace period was %v but was expected to be %v", *out, tc.gracePeriod)
}
})
}
}

Expand Down
10 changes: 10 additions & 0 deletions staging/src/k8s.io/apiserver/pkg/registry/rest/delete.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import (
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apiserver/pkg/admission"
utilpointer "k8s.io/utils/pointer"
)

// RESTDeleteStrategy defines deletion behavior on an object that follows Kubernetes
Expand Down Expand Up @@ -86,6 +87,15 @@ func BeforeDelete(strategy RESTDeleteStrategy, ctx context.Context, obj runtime.
return false, false, errors.NewConflict(schema.GroupResource{Group: gvk.Group, Resource: gvk.Kind}, objectMeta.GetName(), fmt.Errorf("the ResourceVersion in the precondition (%s) does not match the ResourceVersion in record (%s). The object might have been modified", *options.Preconditions.ResourceVersion, objectMeta.GetResourceVersion()))
}
}

// Negative values will be treated as the value `1s` on the delete path.
if gracePeriodSeconds := options.GracePeriodSeconds; gracePeriodSeconds != nil && *gracePeriodSeconds < 0 {
options.GracePeriodSeconds = utilpointer.Int64(1)
}
if deletionGracePeriodSeconds := objectMeta.GetDeletionGracePeriodSeconds(); deletionGracePeriodSeconds != nil && *deletionGracePeriodSeconds < 0 {
objectMeta.SetDeletionGracePeriodSeconds(utilpointer.Int64(1))
}

gracefulStrategy, ok := strategy.(RESTGracefulDeleteStrategy)
if !ok {
// If we're not deleting gracefully there's no point in updating Generation, as we won't update
Expand Down

0 comments on commit 5e06f17

Please sign in to comment.