mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-21 02:41:25 +00:00
Merge pull request #100523 from tkashem/refactor-finish-request
Refactor rest.FinishRequest function
This commit is contained in:
commit
a5489431cf
@ -36,6 +36,7 @@ import (
|
|||||||
"k8s.io/apiserver/pkg/admission"
|
"k8s.io/apiserver/pkg/admission"
|
||||||
"k8s.io/apiserver/pkg/audit"
|
"k8s.io/apiserver/pkg/audit"
|
||||||
"k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager"
|
"k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager"
|
||||||
|
"k8s.io/apiserver/pkg/endpoints/handlers/finisher"
|
||||||
"k8s.io/apiserver/pkg/endpoints/handlers/negotiation"
|
"k8s.io/apiserver/pkg/endpoints/handlers/negotiation"
|
||||||
"k8s.io/apiserver/pkg/endpoints/request"
|
"k8s.io/apiserver/pkg/endpoints/request"
|
||||||
"k8s.io/apiserver/pkg/features"
|
"k8s.io/apiserver/pkg/features"
|
||||||
@ -157,7 +158,7 @@ func createHandler(r rest.NamedCreater, scope *RequestScope, admit admission.Int
|
|||||||
}
|
}
|
||||||
// Dedup owner references before updating managed fields
|
// Dedup owner references before updating managed fields
|
||||||
dedupOwnerReferencesAndAddWarning(obj, req.Context(), false)
|
dedupOwnerReferencesAndAddWarning(obj, req.Context(), false)
|
||||||
result, err := finishRequest(ctx, func() (runtime.Object, error) {
|
result, err := finisher.FinishRequest(ctx, func() (runtime.Object, error) {
|
||||||
if scope.FieldManager != nil {
|
if scope.FieldManager != nil {
|
||||||
liveObj, err := scope.Creater.New(scope.Kind)
|
liveObj, err := scope.Creater.New(scope.Kind)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -32,6 +32,7 @@ import (
|
|||||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||||
"k8s.io/apiserver/pkg/admission"
|
"k8s.io/apiserver/pkg/admission"
|
||||||
"k8s.io/apiserver/pkg/audit"
|
"k8s.io/apiserver/pkg/audit"
|
||||||
|
"k8s.io/apiserver/pkg/endpoints/handlers/finisher"
|
||||||
"k8s.io/apiserver/pkg/endpoints/handlers/negotiation"
|
"k8s.io/apiserver/pkg/endpoints/handlers/negotiation"
|
||||||
"k8s.io/apiserver/pkg/endpoints/request"
|
"k8s.io/apiserver/pkg/endpoints/request"
|
||||||
"k8s.io/apiserver/pkg/features"
|
"k8s.io/apiserver/pkg/features"
|
||||||
@ -124,7 +125,7 @@ func DeleteResource(r rest.GracefulDeleter, allowsOptions bool, scope *RequestSc
|
|||||||
wasDeleted := true
|
wasDeleted := true
|
||||||
userInfo, _ := request.UserFrom(ctx)
|
userInfo, _ := request.UserFrom(ctx)
|
||||||
staticAdmissionAttrs := admission.NewAttributesRecord(nil, nil, scope.Kind, namespace, name, scope.Resource, scope.Subresource, admission.Delete, options, dryrun.IsDryRun(options.DryRun), userInfo)
|
staticAdmissionAttrs := admission.NewAttributesRecord(nil, nil, scope.Kind, namespace, name, scope.Resource, scope.Subresource, admission.Delete, options, dryrun.IsDryRun(options.DryRun), userInfo)
|
||||||
result, err := finishRequest(ctx, func() (runtime.Object, error) {
|
result, err := finisher.FinishRequest(ctx, func() (runtime.Object, error) {
|
||||||
obj, deleted, err := r.Delete(ctx, name, rest.AdmissionToValidateObjectDeleteFunc(admit, staticAdmissionAttrs, scope), options)
|
obj, deleted, err := r.Delete(ctx, name, rest.AdmissionToValidateObjectDeleteFunc(admit, staticAdmissionAttrs, scope), options)
|
||||||
wasDeleted = deleted
|
wasDeleted = deleted
|
||||||
return obj, err
|
return obj, err
|
||||||
@ -267,7 +268,7 @@ func DeleteCollection(r rest.CollectionDeleter, checkBody bool, scope *RequestSc
|
|||||||
admit = admission.WithAudit(admit, ae)
|
admit = admission.WithAudit(admit, ae)
|
||||||
userInfo, _ := request.UserFrom(ctx)
|
userInfo, _ := request.UserFrom(ctx)
|
||||||
staticAdmissionAttrs := admission.NewAttributesRecord(nil, nil, scope.Kind, namespace, "", scope.Resource, scope.Subresource, admission.Delete, options, dryrun.IsDryRun(options.DryRun), userInfo)
|
staticAdmissionAttrs := admission.NewAttributesRecord(nil, nil, scope.Kind, namespace, "", scope.Resource, scope.Subresource, admission.Delete, options, dryrun.IsDryRun(options.DryRun), userInfo)
|
||||||
result, err := finishRequest(ctx, func() (runtime.Object, error) {
|
result, err := finisher.FinishRequest(ctx, func() (runtime.Object, error) {
|
||||||
return r.DeleteCollection(ctx, rest.AdmissionToValidateObjectDeleteFunc(admit, staticAdmissionAttrs, scope), options, &listOptions)
|
return r.DeleteCollection(ctx, rest.AdmissionToValidateObjectDeleteFunc(admit, staticAdmissionAttrs, scope), options, &listOptions)
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -0,0 +1,109 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2021 The Kubernetes Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package finisher
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
goruntime "runtime"
|
||||||
|
|
||||||
|
"k8s.io/apimachinery/pkg/api/errors"
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ResultFunc is a function that returns a rest result and can be run in a goroutine
|
||||||
|
type ResultFunc func() (runtime.Object, error)
|
||||||
|
|
||||||
|
// result stores the return values or panic from a ResultFunc function
|
||||||
|
type result struct {
|
||||||
|
// object stores the response returned by the ResultFunc function
|
||||||
|
object runtime.Object
|
||||||
|
// err stores the error returned by the ResultFunc function
|
||||||
|
err error
|
||||||
|
// reason stores the reason from a panic thrown by the ResultFunc function
|
||||||
|
reason interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return processes the result returned by a ResultFunc function
|
||||||
|
func (r *result) Return() (runtime.Object, error) {
|
||||||
|
switch {
|
||||||
|
case r.reason != nil:
|
||||||
|
// panic has higher precedence, the goroutine executing ResultFunc has panic'd,
|
||||||
|
// so propagate a panic to the caller.
|
||||||
|
panic(r.reason)
|
||||||
|
case r.err != nil:
|
||||||
|
return nil, r.err
|
||||||
|
default:
|
||||||
|
// if we are here, it means neither a panic, nor an error
|
||||||
|
if status, ok := r.object.(*metav1.Status); ok {
|
||||||
|
// An api.Status object with status != success is considered an "error",
|
||||||
|
// which interrupts the normal response flow.
|
||||||
|
if status.Status != metav1.StatusSuccess {
|
||||||
|
return nil, errors.FromObject(status)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return r.object, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// FinishRequest makes a given ResultFunc asynchronous and handles errors returned by the response.
|
||||||
|
func FinishRequest(ctx context.Context, fn ResultFunc) (runtime.Object, error) {
|
||||||
|
// the channel needs to be buffered to prevent the goroutine below from hanging indefinitely
|
||||||
|
// when the select statement reads something other than the one the goroutine sends on.
|
||||||
|
resultCh := make(chan *result, 1)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
result := &result{}
|
||||||
|
|
||||||
|
// panics don't cross goroutine boundaries, so we have to handle ourselves
|
||||||
|
defer func() {
|
||||||
|
reason := recover()
|
||||||
|
if reason != nil {
|
||||||
|
// do not wrap the sentinel ErrAbortHandler panic value
|
||||||
|
if reason != http.ErrAbortHandler {
|
||||||
|
// Same as stdlib http server code. Manually allocate stack
|
||||||
|
// trace buffer size to prevent excessively large logs
|
||||||
|
const size = 64 << 10
|
||||||
|
buf := make([]byte, size)
|
||||||
|
buf = buf[:goruntime.Stack(buf, false)]
|
||||||
|
reason = fmt.Sprintf("%v\n%s", reason, buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
// store the panic reason into the result.
|
||||||
|
result.reason = reason
|
||||||
|
}
|
||||||
|
|
||||||
|
// Propagate the result to the parent goroutine
|
||||||
|
resultCh <- result
|
||||||
|
}()
|
||||||
|
|
||||||
|
if object, err := fn(); err != nil {
|
||||||
|
result.err = err
|
||||||
|
} else {
|
||||||
|
result.object = object
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
select {
|
||||||
|
case result := <-resultCh:
|
||||||
|
return result.Return()
|
||||||
|
case <-ctx.Done():
|
||||||
|
return nil, errors.NewTimeoutError(fmt.Sprintf("request did not complete within requested timeout %s", ctx.Err()), 0)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,158 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2021 The Kubernetes Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package finisher
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"reflect"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
apiequality "k8s.io/apimachinery/pkg/api/equality"
|
||||||
|
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
|
"k8s.io/apiserver/pkg/apis/example"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestFinishRequest(t *testing.T) {
|
||||||
|
exampleObj := &example.Pod{}
|
||||||
|
exampleErr := fmt.Errorf("error")
|
||||||
|
successStatusObj := &metav1.Status{Status: metav1.StatusSuccess, Message: "success message"}
|
||||||
|
errorStatusObj := &metav1.Status{Status: metav1.StatusFailure, Message: "error message"}
|
||||||
|
timeoutFunc := func() (context.Context, context.CancelFunc) {
|
||||||
|
return context.WithTimeout(context.TODO(), time.Second)
|
||||||
|
}
|
||||||
|
|
||||||
|
testcases := []struct {
|
||||||
|
name string
|
||||||
|
timeout func() (context.Context, context.CancelFunc)
|
||||||
|
fn ResultFunc
|
||||||
|
expectedObj runtime.Object
|
||||||
|
expectedErr error
|
||||||
|
expectedPanic string
|
||||||
|
|
||||||
|
expectedPanicObj interface{}
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Expected obj is returned",
|
||||||
|
timeout: timeoutFunc,
|
||||||
|
fn: func() (runtime.Object, error) {
|
||||||
|
return exampleObj, nil
|
||||||
|
},
|
||||||
|
expectedObj: exampleObj,
|
||||||
|
expectedErr: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Expected error is returned",
|
||||||
|
timeout: timeoutFunc,
|
||||||
|
fn: func() (runtime.Object, error) {
|
||||||
|
return nil, exampleErr
|
||||||
|
},
|
||||||
|
expectedObj: nil,
|
||||||
|
expectedErr: exampleErr,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "No expected error or object or panic",
|
||||||
|
timeout: timeoutFunc,
|
||||||
|
fn: func() (runtime.Object, error) {
|
||||||
|
return nil, nil
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Successful status object is returned as expected",
|
||||||
|
timeout: timeoutFunc,
|
||||||
|
fn: func() (runtime.Object, error) {
|
||||||
|
return successStatusObj, nil
|
||||||
|
},
|
||||||
|
expectedObj: successStatusObj,
|
||||||
|
expectedErr: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Error status object is converted to StatusError",
|
||||||
|
timeout: timeoutFunc,
|
||||||
|
fn: func() (runtime.Object, error) {
|
||||||
|
return errorStatusObj, nil
|
||||||
|
},
|
||||||
|
expectedObj: nil,
|
||||||
|
expectedErr: apierrors.FromObject(errorStatusObj),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Panic is propagated up",
|
||||||
|
timeout: timeoutFunc,
|
||||||
|
fn: func() (runtime.Object, error) {
|
||||||
|
panic("my panic")
|
||||||
|
},
|
||||||
|
expectedObj: nil,
|
||||||
|
expectedErr: nil,
|
||||||
|
expectedPanic: "my panic",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Panic is propagated with stack",
|
||||||
|
timeout: timeoutFunc,
|
||||||
|
fn: func() (runtime.Object, error) {
|
||||||
|
panic("my panic")
|
||||||
|
},
|
||||||
|
expectedObj: nil,
|
||||||
|
expectedErr: nil,
|
||||||
|
expectedPanic: "finisher_test.go",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "http.ErrAbortHandler panic is propagated without wrapping with stack",
|
||||||
|
timeout: timeoutFunc,
|
||||||
|
fn: func() (runtime.Object, error) {
|
||||||
|
panic(http.ErrAbortHandler)
|
||||||
|
},
|
||||||
|
expectedObj: nil,
|
||||||
|
expectedErr: nil,
|
||||||
|
expectedPanic: http.ErrAbortHandler.Error(),
|
||||||
|
expectedPanicObj: http.ErrAbortHandler,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for i, tc := range testcases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
ctx, cancel := tc.timeout()
|
||||||
|
defer func() {
|
||||||
|
cancel()
|
||||||
|
|
||||||
|
r := recover()
|
||||||
|
switch {
|
||||||
|
case r == nil && len(tc.expectedPanic) > 0:
|
||||||
|
t.Errorf("expected panic containing '%s', got none", tc.expectedPanic)
|
||||||
|
case r != nil && len(tc.expectedPanic) == 0:
|
||||||
|
t.Errorf("unexpected panic: %v", r)
|
||||||
|
case r != nil && len(tc.expectedPanic) > 0 && !strings.Contains(fmt.Sprintf("%v", r), tc.expectedPanic):
|
||||||
|
t.Errorf("expected panic containing '%s', got '%v'", tc.expectedPanic, r)
|
||||||
|
}
|
||||||
|
|
||||||
|
if tc.expectedPanicObj != nil && !reflect.DeepEqual(tc.expectedPanicObj, r) {
|
||||||
|
t.Errorf("expected panic obj %#v, got %#v", tc.expectedPanicObj, r)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
obj, err := FinishRequest(ctx, tc.fn)
|
||||||
|
if (err == nil && tc.expectedErr != nil) || (err != nil && tc.expectedErr == nil) || (err != nil && tc.expectedErr != nil && err.Error() != tc.expectedErr.Error()) {
|
||||||
|
t.Errorf("%d: unexpected err. expected: %v, got: %v", i, tc.expectedErr, err)
|
||||||
|
}
|
||||||
|
if !apiequality.Semantic.DeepEqual(obj, tc.expectedObj) {
|
||||||
|
t.Errorf("%d: unexpected obj. expected %#v, got %#v", i, tc.expectedObj, obj)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
@ -43,6 +43,7 @@ import (
|
|||||||
"k8s.io/apiserver/pkg/audit"
|
"k8s.io/apiserver/pkg/audit"
|
||||||
"k8s.io/apiserver/pkg/authorization/authorizer"
|
"k8s.io/apiserver/pkg/authorization/authorizer"
|
||||||
"k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager"
|
"k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager"
|
||||||
|
"k8s.io/apiserver/pkg/endpoints/handlers/finisher"
|
||||||
"k8s.io/apiserver/pkg/endpoints/handlers/negotiation"
|
"k8s.io/apiserver/pkg/endpoints/handlers/negotiation"
|
||||||
"k8s.io/apiserver/pkg/endpoints/request"
|
"k8s.io/apiserver/pkg/endpoints/request"
|
||||||
"k8s.io/apiserver/pkg/features"
|
"k8s.io/apiserver/pkg/features"
|
||||||
@ -590,7 +591,7 @@ func (p *patcher) patchResource(ctx context.Context, scope *RequestScope) (runti
|
|||||||
wasCreated = created
|
wasCreated = created
|
||||||
return updateObject, updateErr
|
return updateObject, updateErr
|
||||||
}
|
}
|
||||||
result, err := finishRequest(ctx, func() (runtime.Object, error) {
|
result, err := finisher.FinishRequest(ctx, func() (runtime.Object, error) {
|
||||||
result, err := requestFunc()
|
result, err := requestFunc()
|
||||||
// If the object wasn't committed to storage because it's serialized size was too large,
|
// If the object wasn't committed to storage because it's serialized size was too large,
|
||||||
// it is safe to remove managedFields (which can be large) and try again.
|
// it is safe to remove managedFields (which can be large) and try again.
|
||||||
|
@ -24,7 +24,6 @@ import (
|
|||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
goruntime "runtime"
|
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@ -225,60 +224,6 @@ func (r *responder) Error(err error) {
|
|||||||
r.scope.err(err, r.w, r.req)
|
r.scope.err(err, r.w, r.req)
|
||||||
}
|
}
|
||||||
|
|
||||||
// resultFunc is a function that returns a rest result and can be run in a goroutine
|
|
||||||
type resultFunc func() (runtime.Object, error)
|
|
||||||
|
|
||||||
// finishRequest makes a given resultFunc asynchronous and handles errors returned by the response.
|
|
||||||
// An api.Status object with status != success is considered an "error", which interrupts the normal response flow.
|
|
||||||
func finishRequest(ctx context.Context, fn resultFunc) (result runtime.Object, err error) {
|
|
||||||
// these channels need to be buffered to prevent the goroutine below from hanging indefinitely
|
|
||||||
// when the select statement reads something other than the one the goroutine sends on.
|
|
||||||
ch := make(chan runtime.Object, 1)
|
|
||||||
errCh := make(chan error, 1)
|
|
||||||
panicCh := make(chan interface{}, 1)
|
|
||||||
go func() {
|
|
||||||
// panics don't cross goroutine boundaries, so we have to handle ourselves
|
|
||||||
defer func() {
|
|
||||||
panicReason := recover()
|
|
||||||
if panicReason != nil {
|
|
||||||
// do not wrap the sentinel ErrAbortHandler panic value
|
|
||||||
if panicReason != http.ErrAbortHandler {
|
|
||||||
// Same as stdlib http server code. Manually allocate stack
|
|
||||||
// trace buffer size to prevent excessively large logs
|
|
||||||
const size = 64 << 10
|
|
||||||
buf := make([]byte, size)
|
|
||||||
buf = buf[:goruntime.Stack(buf, false)]
|
|
||||||
panicReason = fmt.Sprintf("%v\n%s", panicReason, buf)
|
|
||||||
}
|
|
||||||
// Propagate to parent goroutine
|
|
||||||
panicCh <- panicReason
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
if result, err := fn(); err != nil {
|
|
||||||
errCh <- err
|
|
||||||
} else {
|
|
||||||
ch <- result
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
select {
|
|
||||||
case result = <-ch:
|
|
||||||
if status, ok := result.(*metav1.Status); ok {
|
|
||||||
if status.Status != metav1.StatusSuccess {
|
|
||||||
return nil, errors.FromObject(status)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result, nil
|
|
||||||
case err = <-errCh:
|
|
||||||
return nil, err
|
|
||||||
case p := <-panicCh:
|
|
||||||
panic(p)
|
|
||||||
case <-ctx.Done():
|
|
||||||
return nil, errors.NewTimeoutError(fmt.Sprintf("request did not complete within requested timeout %s", ctx.Err()), 0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// transformDecodeError adds additional information into a bad-request api error when a decode fails.
|
// transformDecodeError adds additional information into a bad-request api error when a decode fails.
|
||||||
func transformDecodeError(typer runtime.ObjectTyper, baseErr error, into runtime.Object, gvk *schema.GroupVersionKind, body []byte) error {
|
func transformDecodeError(typer runtime.ObjectTyper, baseErr error, into runtime.Object, gvk *schema.GroupVersionKind, body []byte) error {
|
||||||
objGVKs, _, err := typer.ObjectKinds(into)
|
objGVKs, _, err := typer.ObjectKinds(into)
|
||||||
|
@ -826,124 +826,6 @@ func TestHasUID(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestFinishRequest(t *testing.T) {
|
|
||||||
exampleObj := &example.Pod{}
|
|
||||||
exampleErr := fmt.Errorf("error")
|
|
||||||
successStatusObj := &metav1.Status{Status: metav1.StatusSuccess, Message: "success message"}
|
|
||||||
errorStatusObj := &metav1.Status{Status: metav1.StatusFailure, Message: "error message"}
|
|
||||||
timeoutFunc := func() (context.Context, context.CancelFunc) {
|
|
||||||
return context.WithTimeout(context.TODO(), time.Second)
|
|
||||||
}
|
|
||||||
|
|
||||||
testcases := []struct {
|
|
||||||
name string
|
|
||||||
timeout func() (context.Context, context.CancelFunc)
|
|
||||||
fn resultFunc
|
|
||||||
expectedObj runtime.Object
|
|
||||||
expectedErr error
|
|
||||||
expectedPanic string
|
|
||||||
|
|
||||||
expectedPanicObj interface{}
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "Expected obj is returned",
|
|
||||||
timeout: timeoutFunc,
|
|
||||||
fn: func() (runtime.Object, error) {
|
|
||||||
return exampleObj, nil
|
|
||||||
},
|
|
||||||
expectedObj: exampleObj,
|
|
||||||
expectedErr: nil,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Expected error is returned",
|
|
||||||
timeout: timeoutFunc,
|
|
||||||
fn: func() (runtime.Object, error) {
|
|
||||||
return nil, exampleErr
|
|
||||||
},
|
|
||||||
expectedObj: nil,
|
|
||||||
expectedErr: exampleErr,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Successful status object is returned as expected",
|
|
||||||
timeout: timeoutFunc,
|
|
||||||
fn: func() (runtime.Object, error) {
|
|
||||||
return successStatusObj, nil
|
|
||||||
},
|
|
||||||
expectedObj: successStatusObj,
|
|
||||||
expectedErr: nil,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Error status object is converted to StatusError",
|
|
||||||
timeout: timeoutFunc,
|
|
||||||
fn: func() (runtime.Object, error) {
|
|
||||||
return errorStatusObj, nil
|
|
||||||
},
|
|
||||||
expectedObj: nil,
|
|
||||||
expectedErr: apierrors.FromObject(errorStatusObj),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Panic is propagated up",
|
|
||||||
timeout: timeoutFunc,
|
|
||||||
fn: func() (runtime.Object, error) {
|
|
||||||
panic("my panic")
|
|
||||||
},
|
|
||||||
expectedObj: nil,
|
|
||||||
expectedErr: nil,
|
|
||||||
expectedPanic: "my panic",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Panic is propagated with stack",
|
|
||||||
timeout: timeoutFunc,
|
|
||||||
fn: func() (runtime.Object, error) {
|
|
||||||
panic("my panic")
|
|
||||||
},
|
|
||||||
expectedObj: nil,
|
|
||||||
expectedErr: nil,
|
|
||||||
expectedPanic: "rest_test.go",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "http.ErrAbortHandler panic is propagated without wrapping with stack",
|
|
||||||
timeout: timeoutFunc,
|
|
||||||
fn: func() (runtime.Object, error) {
|
|
||||||
panic(http.ErrAbortHandler)
|
|
||||||
},
|
|
||||||
expectedObj: nil,
|
|
||||||
expectedErr: nil,
|
|
||||||
expectedPanic: http.ErrAbortHandler.Error(),
|
|
||||||
expectedPanicObj: http.ErrAbortHandler,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for i, tc := range testcases {
|
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
|
||||||
ctx, cancel := tc.timeout()
|
|
||||||
defer func() {
|
|
||||||
cancel()
|
|
||||||
|
|
||||||
r := recover()
|
|
||||||
switch {
|
|
||||||
case r == nil && len(tc.expectedPanic) > 0:
|
|
||||||
t.Errorf("expected panic containing '%s', got none", tc.expectedPanic)
|
|
||||||
case r != nil && len(tc.expectedPanic) == 0:
|
|
||||||
t.Errorf("unexpected panic: %v", r)
|
|
||||||
case r != nil && len(tc.expectedPanic) > 0 && !strings.Contains(fmt.Sprintf("%v", r), tc.expectedPanic):
|
|
||||||
t.Errorf("expected panic containing '%s', got '%v'", tc.expectedPanic, r)
|
|
||||||
}
|
|
||||||
|
|
||||||
if tc.expectedPanicObj != nil && !reflect.DeepEqual(tc.expectedPanicObj, r) {
|
|
||||||
t.Errorf("expected panic obj %#v, got %#v", tc.expectedPanicObj, r)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
obj, err := finishRequest(ctx, tc.fn)
|
|
||||||
if (err == nil && tc.expectedErr != nil) || (err != nil && tc.expectedErr == nil) || (err != nil && tc.expectedErr != nil && err.Error() != tc.expectedErr.Error()) {
|
|
||||||
t.Errorf("%d: unexpected err. expected: %v, got: %v", i, tc.expectedErr, err)
|
|
||||||
}
|
|
||||||
if !apiequality.Semantic.DeepEqual(obj, tc.expectedObj) {
|
|
||||||
t.Errorf("%d: unexpected obj. expected %#v, got %#v", i, tc.expectedObj, obj)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func setTcPod(tcPod *example.Pod, name string, namespace string, uid types.UID, resourceVersion string, apiVersion string, activeDeadlineSeconds *int64, nodeName string) {
|
func setTcPod(tcPod *example.Pod, name string, namespace string, uid types.UID, resourceVersion string, apiVersion string, activeDeadlineSeconds *int64, nodeName string) {
|
||||||
tcPod.Name = name
|
tcPod.Name = name
|
||||||
tcPod.Namespace = namespace
|
tcPod.Namespace = namespace
|
||||||
|
@ -34,6 +34,7 @@ import (
|
|||||||
"k8s.io/apiserver/pkg/audit"
|
"k8s.io/apiserver/pkg/audit"
|
||||||
"k8s.io/apiserver/pkg/authorization/authorizer"
|
"k8s.io/apiserver/pkg/authorization/authorizer"
|
||||||
"k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager"
|
"k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager"
|
||||||
|
"k8s.io/apiserver/pkg/endpoints/handlers/finisher"
|
||||||
"k8s.io/apiserver/pkg/endpoints/handlers/negotiation"
|
"k8s.io/apiserver/pkg/endpoints/handlers/negotiation"
|
||||||
"k8s.io/apiserver/pkg/endpoints/request"
|
"k8s.io/apiserver/pkg/endpoints/request"
|
||||||
"k8s.io/apiserver/pkg/features"
|
"k8s.io/apiserver/pkg/features"
|
||||||
@ -198,7 +199,7 @@ func UpdateResource(r rest.Updater, scope *RequestScope, admit admission.Interfa
|
|||||||
}
|
}
|
||||||
// Dedup owner references before updating managed fields
|
// Dedup owner references before updating managed fields
|
||||||
dedupOwnerReferencesAndAddWarning(obj, req.Context(), false)
|
dedupOwnerReferencesAndAddWarning(obj, req.Context(), false)
|
||||||
result, err := finishRequest(ctx, func() (runtime.Object, error) {
|
result, err := finisher.FinishRequest(ctx, func() (runtime.Object, error) {
|
||||||
result, err := requestFunc()
|
result, err := requestFunc()
|
||||||
// If the object wasn't committed to storage because it's serialized size was too large,
|
// If the object wasn't committed to storage because it's serialized size was too large,
|
||||||
// it is safe to remove managedFields (which can be large) and try again.
|
// it is safe to remove managedFields (which can be large) and try again.
|
||||||
|
1
vendor/modules.txt
vendored
1
vendor/modules.txt
vendored
@ -1818,6 +1818,7 @@ k8s.io/apiserver/pkg/endpoints/filters
|
|||||||
k8s.io/apiserver/pkg/endpoints/handlers
|
k8s.io/apiserver/pkg/endpoints/handlers
|
||||||
k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager
|
k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager
|
||||||
k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/internal
|
k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/internal
|
||||||
|
k8s.io/apiserver/pkg/endpoints/handlers/finisher
|
||||||
k8s.io/apiserver/pkg/endpoints/handlers/negotiation
|
k8s.io/apiserver/pkg/endpoints/handlers/negotiation
|
||||||
k8s.io/apiserver/pkg/endpoints/handlers/responsewriters
|
k8s.io/apiserver/pkg/endpoints/handlers/responsewriters
|
||||||
k8s.io/apiserver/pkg/endpoints/metrics
|
k8s.io/apiserver/pkg/endpoints/metrics
|
||||||
|
Loading…
Reference in New Issue
Block a user