mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-09-14 21:53:52 +00:00
Errors should be part of api/errors, not apiserver
Renames constructor methods to be errors.New<name> which changed a few places.
This commit is contained in:
@@ -31,6 +31,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
|
||||
apierrs "github.com/GoogleCloudPlatform/kubernetes/pkg/api/errors"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/version"
|
||||
@@ -331,7 +332,7 @@ func TestGet(t *testing.T) {
|
||||
func TestGetMissing(t *testing.T) {
|
||||
storage := map[string]RESTStorage{}
|
||||
simpleStorage := SimpleRESTStorage{
|
||||
errors: map[string]error{"get": NewNotFoundErr("simple", "id")},
|
||||
errors: map[string]error{"get": apierrs.NewNotFound("simple", "id")},
|
||||
}
|
||||
storage["simple"] = &simpleStorage
|
||||
handler := Handle(storage, codec, "/prefix/version")
|
||||
@@ -371,7 +372,7 @@ func TestDeleteMissing(t *testing.T) {
|
||||
storage := map[string]RESTStorage{}
|
||||
ID := "id"
|
||||
simpleStorage := SimpleRESTStorage{
|
||||
errors: map[string]error{"delete": NewNotFoundErr("simple", ID)},
|
||||
errors: map[string]error{"delete": apierrs.NewNotFound("simple", ID)},
|
||||
}
|
||||
storage["simple"] = &simpleStorage
|
||||
handler := Handle(storage, codec, "/prefix/version")
|
||||
@@ -421,7 +422,7 @@ func TestUpdateMissing(t *testing.T) {
|
||||
storage := map[string]RESTStorage{}
|
||||
ID := "id"
|
||||
simpleStorage := SimpleRESTStorage{
|
||||
errors: map[string]error{"update": NewNotFoundErr("simple", ID)},
|
||||
errors: map[string]error{"update": apierrs.NewNotFound("simple", ID)},
|
||||
}
|
||||
storage["simple"] = &simpleStorage
|
||||
handler := Handle(storage, codec, "/prefix/version")
|
||||
@@ -490,7 +491,7 @@ func TestCreateNotFound(t *testing.T) {
|
||||
"simple": &SimpleRESTStorage{
|
||||
// storage.Create can fail with not found error in theory.
|
||||
// See https://github.com/GoogleCloudPlatform/kubernetes/pull/486#discussion_r15037092.
|
||||
errors: map[string]error{"create": NewNotFoundErr("simple", "id")},
|
||||
errors: map[string]error{"create": apierrs.NewNotFound("simple", "id")},
|
||||
},
|
||||
}, codec, "/prefix/version")
|
||||
server := httptest.NewServer(handler)
|
||||
@@ -597,41 +598,10 @@ func expectApiStatus(t *testing.T, method, url string, data []byte, code int) *a
|
||||
return &status
|
||||
}
|
||||
|
||||
func TestErrorsToAPIStatus(t *testing.T) {
|
||||
cases := map[error]api.Status{
|
||||
NewAlreadyExistsErr("foo", "bar"): {
|
||||
Status: api.StatusFailure,
|
||||
Code: http.StatusConflict,
|
||||
Reason: "already_exists",
|
||||
Message: "foo \"bar\" already exists",
|
||||
Details: &api.StatusDetails{
|
||||
Kind: "foo",
|
||||
ID: "bar",
|
||||
},
|
||||
},
|
||||
NewConflictErr("foo", "bar", errors.New("failure")): {
|
||||
Status: api.StatusFailure,
|
||||
Code: http.StatusConflict,
|
||||
Reason: "conflict",
|
||||
Message: "foo \"bar\" cannot be updated: failure",
|
||||
Details: &api.StatusDetails{
|
||||
Kind: "foo",
|
||||
ID: "bar",
|
||||
},
|
||||
},
|
||||
}
|
||||
for k, v := range cases {
|
||||
actual := errToAPIStatus(k)
|
||||
if !reflect.DeepEqual(actual, &v) {
|
||||
t.Errorf("Expected %#v, Got %#v", v, actual)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestAsyncDelayReturnsError(t *testing.T) {
|
||||
storage := SimpleRESTStorage{
|
||||
injectedFunction: func(obj interface{}) (interface{}, error) {
|
||||
return nil, NewAlreadyExistsErr("foo", "bar")
|
||||
return nil, apierrs.NewAlreadyExists("foo", "bar")
|
||||
},
|
||||
}
|
||||
handler := Handle(map[string]RESTStorage{"foo": &storage}, codec, "/prefix/version")
|
||||
@@ -649,7 +619,7 @@ func TestAsyncCreateError(t *testing.T) {
|
||||
storage := SimpleRESTStorage{
|
||||
injectedFunction: func(obj interface{}) (interface{}, error) {
|
||||
<-ch
|
||||
return nil, NewAlreadyExistsErr("foo", "bar")
|
||||
return nil, apierrs.NewAlreadyExists("foo", "bar")
|
||||
},
|
||||
}
|
||||
handler := Handle(map[string]RESTStorage{"foo": &storage}, codec, "/prefix/version")
|
||||
@@ -673,7 +643,7 @@ func TestAsyncCreateError(t *testing.T) {
|
||||
time.Sleep(time.Millisecond)
|
||||
|
||||
finalStatus := expectApiStatus(t, "GET", fmt.Sprintf("%s/prefix/version/operations/%s?after=1", server.URL, status.Details.ID), []byte{}, http.StatusOK)
|
||||
expectedErr := NewAlreadyExistsErr("foo", "bar")
|
||||
expectedErr := apierrs.NewAlreadyExists("foo", "bar")
|
||||
expectedStatus := &api.Status{
|
||||
Status: api.StatusFailure,
|
||||
Code: http.StatusConflict,
|
||||
|
@@ -21,120 +21,19 @@ import (
|
||||
"net/http"
|
||||
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/errors"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/tools"
|
||||
)
|
||||
|
||||
// apiServerError is an error intended for consumption by a REST API server.
|
||||
type apiServerError struct {
|
||||
api.Status
|
||||
}
|
||||
|
||||
// Error implements the Error interface.
|
||||
func (e *apiServerError) Error() string {
|
||||
return e.Status.Message
|
||||
}
|
||||
|
||||
// NewNotFoundErr returns a new error which indicates that the resource of the kind and the name was not found.
|
||||
func NewNotFoundErr(kind, name string) error {
|
||||
return &apiServerError{api.Status{
|
||||
Status: api.StatusFailure,
|
||||
Code: http.StatusNotFound,
|
||||
Reason: api.StatusReasonNotFound,
|
||||
Details: &api.StatusDetails{
|
||||
Kind: kind,
|
||||
ID: name,
|
||||
},
|
||||
Message: fmt.Sprintf("%s %q not found", kind, name),
|
||||
}}
|
||||
}
|
||||
|
||||
// NewAlreadyExistsErr returns an error indicating the item requested exists by that identifier.
|
||||
func NewAlreadyExistsErr(kind, name string) error {
|
||||
return &apiServerError{api.Status{
|
||||
Status: api.StatusFailure,
|
||||
Code: http.StatusConflict,
|
||||
Reason: api.StatusReasonAlreadyExists,
|
||||
Details: &api.StatusDetails{
|
||||
Kind: kind,
|
||||
ID: name,
|
||||
},
|
||||
Message: fmt.Sprintf("%s %q already exists", kind, name),
|
||||
}}
|
||||
}
|
||||
|
||||
// NewConflictErr returns an error indicating the item can't be updated as provided.
|
||||
func NewConflictErr(kind, name string, err error) error {
|
||||
return &apiServerError{api.Status{
|
||||
Status: api.StatusFailure,
|
||||
Code: http.StatusConflict,
|
||||
Reason: api.StatusReasonConflict,
|
||||
Details: &api.StatusDetails{
|
||||
Kind: kind,
|
||||
ID: name,
|
||||
},
|
||||
Message: fmt.Sprintf("%s %q cannot be updated: %s", kind, name, err),
|
||||
}}
|
||||
}
|
||||
|
||||
// NewInvalidErr returns an error indicating the item is invalid and cannot be processed.
|
||||
func NewInvalidErr(kind, name string, errs errors.ErrorList) error {
|
||||
causes := make([]api.StatusCause, 0, len(errs))
|
||||
for i := range errs {
|
||||
if err, ok := errs[i].(errors.ValidationError); ok {
|
||||
causes = append(causes, api.StatusCause{
|
||||
Type: api.CauseType(err.Type),
|
||||
Message: err.Error(),
|
||||
Field: err.Field,
|
||||
})
|
||||
}
|
||||
}
|
||||
return &apiServerError{api.Status{
|
||||
Status: api.StatusFailure,
|
||||
Code: 422, // RFC 4918
|
||||
Reason: api.StatusReasonInvalid,
|
||||
Details: &api.StatusDetails{
|
||||
Kind: kind,
|
||||
ID: name,
|
||||
Causes: causes,
|
||||
},
|
||||
Message: fmt.Sprintf("%s %q is invalid: %s", kind, name, errs.ToError()),
|
||||
}}
|
||||
}
|
||||
|
||||
// IsNotFound returns true if the specified error was created by NewNotFoundErr.
|
||||
func IsNotFound(err error) bool {
|
||||
return reasonForError(err) == api.StatusReasonNotFound
|
||||
}
|
||||
|
||||
// IsAlreadyExists determines if the err is an error which indicates that a specified resource already exists.
|
||||
func IsAlreadyExists(err error) bool {
|
||||
return reasonForError(err) == api.StatusReasonAlreadyExists
|
||||
}
|
||||
|
||||
// IsConflict determines if the err is an error which indicates the provided update conflicts.
|
||||
func IsConflict(err error) bool {
|
||||
return reasonForError(err) == api.StatusReasonConflict
|
||||
}
|
||||
|
||||
// IsInvalid determines if the err is an error which indicates the provided resource is not valid.
|
||||
func IsInvalid(err error) bool {
|
||||
return reasonForError(err) == api.StatusReasonInvalid
|
||||
}
|
||||
|
||||
func reasonForError(err error) api.StatusReason {
|
||||
switch t := err.(type) {
|
||||
case *apiServerError:
|
||||
return t.Status.Reason
|
||||
}
|
||||
return api.StatusReasonUnknown
|
||||
// statusError is an object that can be converted into an api.Status
|
||||
type statusError interface {
|
||||
Status() api.Status
|
||||
}
|
||||
|
||||
// errToAPIStatus converts an error to an api.Status object.
|
||||
func errToAPIStatus(err error) *api.Status {
|
||||
switch t := err.(type) {
|
||||
case *apiServerError:
|
||||
status := t.Status
|
||||
case statusError:
|
||||
status := t.Status()
|
||||
status.Status = api.StatusFailure
|
||||
//TODO: check for invalid responses
|
||||
return &status
|
||||
|
@@ -17,126 +17,50 @@ limitations under the License.
|
||||
package apiserver
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
stderrs "errors"
|
||||
"net/http"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
|
||||
apierrors "github.com/GoogleCloudPlatform/kubernetes/pkg/api/errors"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/errors"
|
||||
)
|
||||
|
||||
func TestErrorNew(t *testing.T) {
|
||||
err := NewAlreadyExistsErr("test", "1")
|
||||
if !IsAlreadyExists(err) {
|
||||
t.Errorf("expected to be already_exists")
|
||||
}
|
||||
if IsConflict(err) {
|
||||
t.Errorf("expected to not be confict")
|
||||
}
|
||||
if IsNotFound(err) {
|
||||
t.Errorf(fmt.Sprintf("expected to not be %s", api.StatusReasonNotFound))
|
||||
}
|
||||
if IsInvalid(err) {
|
||||
t.Errorf("expected to not be invalid")
|
||||
}
|
||||
|
||||
if !IsConflict(NewConflictErr("test", "2", errors.New("message"))) {
|
||||
t.Errorf("expected to be conflict")
|
||||
}
|
||||
if !IsNotFound(NewNotFoundErr("test", "3")) {
|
||||
t.Errorf("expected to be not found")
|
||||
}
|
||||
if !IsInvalid(NewInvalidErr("test", "2", nil)) {
|
||||
t.Errorf("expected to be invalid")
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewInvalidErr(t *testing.T) {
|
||||
testCases := []struct {
|
||||
Err apierrors.ValidationError
|
||||
Details *api.StatusDetails
|
||||
}{
|
||||
{
|
||||
apierrors.NewDuplicate("field[0].name", "bar"),
|
||||
&api.StatusDetails{
|
||||
Kind: "kind",
|
||||
ID: "name",
|
||||
Causes: []api.StatusCause{{
|
||||
Type: api.CauseTypeFieldValueDuplicate,
|
||||
Field: "field[0].name",
|
||||
}},
|
||||
},
|
||||
},
|
||||
{
|
||||
apierrors.NewInvalid("field[0].name", "bar"),
|
||||
&api.StatusDetails{
|
||||
Kind: "kind",
|
||||
ID: "name",
|
||||
Causes: []api.StatusCause{{
|
||||
Type: api.CauseTypeFieldValueInvalid,
|
||||
Field: "field[0].name",
|
||||
}},
|
||||
},
|
||||
},
|
||||
{
|
||||
apierrors.NewNotFound("field[0].name", "bar"),
|
||||
&api.StatusDetails{
|
||||
Kind: "kind",
|
||||
ID: "name",
|
||||
Causes: []api.StatusCause{{
|
||||
Type: api.CauseTypeFieldValueNotFound,
|
||||
Field: "field[0].name",
|
||||
}},
|
||||
},
|
||||
},
|
||||
{
|
||||
apierrors.NewNotSupported("field[0].name", "bar"),
|
||||
&api.StatusDetails{
|
||||
Kind: "kind",
|
||||
ID: "name",
|
||||
Causes: []api.StatusCause{{
|
||||
Type: api.CauseTypeFieldValueNotSupported,
|
||||
Field: "field[0].name",
|
||||
}},
|
||||
},
|
||||
},
|
||||
{
|
||||
apierrors.NewRequired("field[0].name", "bar"),
|
||||
&api.StatusDetails{
|
||||
Kind: "kind",
|
||||
ID: "name",
|
||||
Causes: []api.StatusCause{{
|
||||
Type: api.CauseTypeFieldValueRequired,
|
||||
Field: "field[0].name",
|
||||
}},
|
||||
},
|
||||
},
|
||||
}
|
||||
for i := range testCases {
|
||||
vErr, expected := testCases[i].Err, testCases[i].Details
|
||||
expected.Causes[0].Message = vErr.Error()
|
||||
err := NewInvalidErr("kind", "name", apierrors.ErrorList{vErr})
|
||||
status := errToAPIStatus(err)
|
||||
if status.Code != 422 || status.Reason != api.StatusReasonInvalid {
|
||||
t.Errorf("unexpected status: %#v", status)
|
||||
}
|
||||
if !reflect.DeepEqual(expected, status.Details) {
|
||||
t.Errorf("expected %#v, got %#v", expected, status.Details)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func Test_errToAPIStatus(t *testing.T) {
|
||||
err := &apiServerError{}
|
||||
err := errors.NewNotFound("foo", "bar")
|
||||
status := errToAPIStatus(err)
|
||||
if status.Reason != api.StatusReasonUnknown || status.Status != api.StatusFailure {
|
||||
if status.Reason != api.StatusReasonNotFound || status.Status != api.StatusFailure {
|
||||
t.Errorf("unexpected status object: %#v", status)
|
||||
}
|
||||
}
|
||||
|
||||
func Test_reasonForError(t *testing.T) {
|
||||
if e, a := api.StatusReasonUnknown, reasonForError(nil); e != a {
|
||||
t.Errorf("unexpected reason type: %#v", a)
|
||||
func TestErrorsToAPIStatus(t *testing.T) {
|
||||
cases := map[error]api.Status{
|
||||
errors.NewAlreadyExists("foo", "bar"): {
|
||||
Status: api.StatusFailure,
|
||||
Code: http.StatusConflict,
|
||||
Reason: "already_exists",
|
||||
Message: "foo \"bar\" already exists",
|
||||
Details: &api.StatusDetails{
|
||||
Kind: "foo",
|
||||
ID: "bar",
|
||||
},
|
||||
},
|
||||
errors.NewConflict("foo", "bar", stderrs.New("failure")): {
|
||||
Status: api.StatusFailure,
|
||||
Code: http.StatusConflict,
|
||||
Reason: "conflict",
|
||||
Message: "foo \"bar\" cannot be updated: failure",
|
||||
Details: &api.StatusDetails{
|
||||
Kind: "foo",
|
||||
ID: "bar",
|
||||
},
|
||||
},
|
||||
}
|
||||
for k, v := range cases {
|
||||
actual := errToAPIStatus(k)
|
||||
if !reflect.DeepEqual(actual, &v) {
|
||||
t.Errorf("%s: Expected %#v, Got %#v", k, v, actual)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user