diff --git a/pkg/api/errors/errors.go b/pkg/api/errors/errors.go index 149653e0226..65791c0af7b 100644 --- a/pkg/api/errors/errors.go +++ b/pkg/api/errors/errors.go @@ -18,138 +18,117 @@ package errors import ( "fmt" - "strings" + "net/http" - "github.com/golang/glog" + "github.com/GoogleCloudPlatform/kubernetes/pkg/api" ) -// ValidationErrorType is a machine readable value providing more detail about why -// a field is invalid. These values are expected to match 1-1 with -// CauseType in api/types.go. -type ValidationErrorType string - -const ( - // ValidationErrorTypeNotFound is used to report failure to find a requested value - // (e.g. looking up an ID). - ValidationErrorTypeNotFound ValidationErrorType = "fieldValueNotFound" - // ValidationErrorTypeRequired is used to report required values that are not - // provided (e.g. empty strings, null values, or empty arrays). - ValidationErrorTypeRequired ValidationErrorType = "fieldValueRequired" - // ValidationErrorTypeDuplicate is used to report collisions of values that must be - // unique (e.g. unique IDs). - ValidationErrorTypeDuplicate ValidationErrorType = "fieldValueDuplicate" - // ValidationErrorTypeInvalid is used to report malformed values (e.g. failed regex - // match). - ValidationErrorTypeInvalid ValidationErrorType = "fieldValueInvalid" - // ValidationErrorTypeNotSupported is used to report valid (as per formatting rules) - // values that can not be handled (e.g. an enumerated string). - ValidationErrorTypeNotSupported ValidationErrorType = "fieldValueNotSupported" -) - -func ValueOf(t ValidationErrorType) string { - switch t { - case ValidationErrorTypeNotFound: - return "not found" - case ValidationErrorTypeRequired: - return "required value" - case ValidationErrorTypeDuplicate: - return "duplicate value" - case ValidationErrorTypeInvalid: - return "invalid value" - case ValidationErrorTypeNotSupported: - return "unsupported value" - default: - glog.Errorf("unrecognized validation type: %#v", t) - return "" - } +// statusError is an error intended for consumption by a REST API server. +type statusError struct { + status api.Status } -// ValidationError is an implementation of the 'error' interface, which represents an error of validation. -type ValidationError struct { - Type ValidationErrorType - Field string - BadValue interface{} +// Error implements the Error interface. +func (e *statusError) Error() string { + return e.status.Message } -func (v ValidationError) Error() string { - return fmt.Sprintf("%s: %v '%v'", v.Field, ValueOf(v.Type), v.BadValue) +// Status converts this error into an api.Status object. +func (e *statusError) Status() api.Status { + return e.status } -// NewInvalid returns a ValidationError indicating "value required" -func NewRequired(field string, value interface{}) ValidationError { - return ValidationError{ValidationErrorTypeRequired, field, value} +// NewNotFound returns a new error which indicates that the resource of the kind and the name was not found. +func NewNotFound(kind, name string) error { + return &statusError{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), + }} } -// NewInvalid returns a ValidationError indicating "invalid value" -func NewInvalid(field string, value interface{}) ValidationError { - return ValidationError{ValidationErrorTypeInvalid, field, value} +// NewAlreadyExists returns an error indicating the item requested exists by that identifier. +func NewAlreadyExists(kind, name string) error { + return &statusError{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), + }} } -// NewNotSupported returns a ValidationError indicating "unsupported value" -func NewNotSupported(field string, value interface{}) ValidationError { - return ValidationError{ValidationErrorTypeNotSupported, field, value} +// NewConflict returns an error indicating the item can't be updated as provided. +func NewConflict(kind, name string, err error) error { + return &statusError{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), + }} } -// NewDuplicate returns a ValidationError indicating "duplicate value" -func NewDuplicate(field string, value interface{}) ValidationError { - return ValidationError{ValidationErrorTypeDuplicate, field, value} -} - -// NewNotFound returns a ValidationError indicating "value not found" -func NewNotFound(field string, value interface{}) ValidationError { - return ValidationError{ValidationErrorTypeNotFound, field, value} -} - -// ErrorList is a collection of errors. This does not implement the error -// interface to avoid confusion where an empty ErrorList would still be an -// error (non-nil). To produce a single error instance from an ErrorList, use -// the ToError() method, which will return nil for an empty ErrorList. -type ErrorList []error - -// This helper implements the error interface for ErrorList, but prevents -// accidental conversion of ErrorList to error. -type errorListInternal ErrorList - -// Error is part of the error interface. -func (list errorListInternal) Error() string { - if len(list) == 0 { - return "" - } - sl := make([]string, len(list)) - for i := range list { - sl[i] = list[i].Error() - } - return strings.Join(sl, "; ") -} - -// ToError converts an ErrorList into a "normal" error, or nil if the list is empty. -func (list ErrorList) ToError() error { - if len(list) == 0 { - return nil - } - return errorListInternal(list) -} - -// Prefix adds a prefix to the Field of every ValidationError in the list. Returns -// the list for convenience. -func (list ErrorList) Prefix(prefix string) ErrorList { - for i := range list { - if err, ok := list[i].(ValidationError); ok { - if strings.HasPrefix(err.Field, "[") { - err.Field = prefix + err.Field - } else if len(err.Field) != 0 { - err.Field = prefix + "." + err.Field - } else { - err.Field = prefix - } - list[i] = err +// NewInvalid returns an error indicating the item is invalid and cannot be processed. +func NewInvalid(kind, name string, errs ErrorList) error { + causes := make([]api.StatusCause, 0, len(errs)) + for i := range errs { + if err, ok := errs[i].(ValidationError); ok { + causes = append(causes, api.StatusCause{ + Type: api.CauseType(err.Type), + Message: err.Error(), + Field: err.Field, + }) } } - return list + return &statusError{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()), + }} } -// PrefixIndex adds an index to the Field of every ValidationError in the list. Returns -// the list for convenience. -func (list ErrorList) PrefixIndex(index int) ErrorList { - return list.Prefix(fmt.Sprintf("[%d]", index)) +// 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 *statusError: + return t.status.Reason + } + return api.StatusReasonUnknown } diff --git a/pkg/api/errors/errors_test.go b/pkg/api/errors/errors_test.go index bd4a72a1541..cde0ea3dfb5 100644 --- a/pkg/api/errors/errors_test.go +++ b/pkg/api/errors/errors_test.go @@ -17,147 +17,117 @@ limitations under the License. package errors import ( + "errors" "fmt" - "strings" + "reflect" "testing" + + "github.com/GoogleCloudPlatform/kubernetes/pkg/api" ) -func TestMakeFuncs(t *testing.T) { +func TestErrorNew(t *testing.T) { + err := NewAlreadyExists("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(NewConflict("test", "2", errors.New("message"))) { + t.Errorf("expected to be conflict") + } + if !IsNotFound(NewNotFound("test", "3")) { + t.Errorf("expected to be not found") + } + if !IsInvalid(NewInvalid("test", "2", nil)) { + t.Errorf("expected to be invalid") + } +} + +func TestNewInvalid(t *testing.T) { testCases := []struct { - fn func() ValidationError - expected ValidationErrorType + Err ValidationError + Details *api.StatusDetails }{ { - func() ValidationError { return NewInvalid("f", "v") }, - ValidationErrorTypeInvalid, + NewFieldDuplicate("field[0].name", "bar"), + &api.StatusDetails{ + Kind: "kind", + ID: "name", + Causes: []api.StatusCause{{ + Type: api.CauseTypeFieldValueDuplicate, + Field: "field[0].name", + }}, + }, }, { - func() ValidationError { return NewNotSupported("f", "v") }, - ValidationErrorTypeNotSupported, + NewFieldInvalid("field[0].name", "bar"), + &api.StatusDetails{ + Kind: "kind", + ID: "name", + Causes: []api.StatusCause{{ + Type: api.CauseTypeFieldValueInvalid, + Field: "field[0].name", + }}, + }, }, { - func() ValidationError { return NewDuplicate("f", "v") }, - ValidationErrorTypeDuplicate, + NewFieldNotFound("field[0].name", "bar"), + &api.StatusDetails{ + Kind: "kind", + ID: "name", + Causes: []api.StatusCause{{ + Type: api.CauseTypeFieldValueNotFound, + Field: "field[0].name", + }}, + }, }, { - func() ValidationError { return NewNotFound("f", "v") }, - ValidationErrorTypeNotFound, + NewFieldNotSupported("field[0].name", "bar"), + &api.StatusDetails{ + Kind: "kind", + ID: "name", + Causes: []api.StatusCause{{ + Type: api.CauseTypeFieldValueNotSupported, + Field: "field[0].name", + }}, + }, }, { - func() ValidationError { return NewRequired("f", "v") }, - ValidationErrorTypeRequired, + NewFieldRequired("field[0].name", "bar"), + &api.StatusDetails{ + Kind: "kind", + ID: "name", + Causes: []api.StatusCause{{ + Type: api.CauseTypeFieldValueRequired, + Field: "field[0].name", + }}, + }, }, } - - for _, testCase := range testCases { - err := testCase.fn() - if err.Type != testCase.expected { - t.Errorf("expected Type %q, got %q", testCase.expected, err.Type) + for i, testCase := range testCases { + vErr, expected := testCase.Err, testCase.Details + expected.Causes[0].Message = vErr.Error() + err := NewInvalid("kind", "name", ErrorList{vErr}) + status := err.(*statusError).Status() + if status.Code != 422 || status.Reason != api.StatusReasonInvalid { + t.Errorf("%d: unexpected status: %#v", i, status) + } + if !reflect.DeepEqual(expected, status.Details) { + t.Errorf("%d: expected %#v, got %#v", expected, status.Details) } } } -func TestValidationError(t *testing.T) { - s := NewInvalid("foo", "bar").Error() - if !strings.Contains(s, "foo") || !strings.Contains(s, "bar") || !strings.Contains(s, ValueOf(ValidationErrorTypeInvalid)) { - t.Errorf("error message did not contain expected values, got %s", s) - } -} - -func TestErrorList(t *testing.T) { - errList := ErrorList{} - if a := errList.ToError(); a != nil { - t.Errorf("unexpected non-nil error for empty list: %v", a) - } - if a := errorListInternal(errList).Error(); a != "" { - t.Errorf("expected empty string, got %v", a) - } - errList = append(errList, NewInvalid("field", "value")) - // The fact that this compiles is the test. -} - -func TestErrorListToError(t *testing.T) { - errList := ErrorList{} - err := errList.ToError() - if err != nil { - t.Errorf("expected nil, got %v", err) - } - - testCases := []struct { - errs ErrorList - expected string - }{ - {ErrorList{fmt.Errorf("abc")}, "abc"}, - {ErrorList{fmt.Errorf("abc"), fmt.Errorf("123")}, "abc; 123"}, - } - for _, testCase := range testCases { - err := testCase.errs.ToError() - if err == nil { - t.Errorf("expected an error, got nil: ErrorList=%v", testCase) - continue - } - if err.Error() != testCase.expected { - t.Errorf("expected %q, got %q", testCase.expected, err.Error()) - } - } -} - -func TestErrListPrefix(t *testing.T) { - testCases := []struct { - Err ValidationError - Expected string - }{ - { - NewNotFound("[0].bar", "value"), - "foo[0].bar", - }, - { - NewInvalid("field", "value"), - "foo.field", - }, - { - NewDuplicate("", "value"), - "foo", - }, - } - for _, testCase := range testCases { - errList := ErrorList{testCase.Err} - prefix := errList.Prefix("foo") - if prefix == nil || len(prefix) != len(errList) { - t.Errorf("Prefix should return self") - } - if e, a := testCase.Expected, errList[0].(ValidationError).Field; e != a { - t.Errorf("expected %s, got %s", e, a) - } - } -} - -func TestErrListPrefixIndex(t *testing.T) { - testCases := []struct { - Err ValidationError - Expected string - }{ - { - NewNotFound("[0].bar", "value"), - "[1][0].bar", - }, - { - NewInvalid("field", "value"), - "[1].field", - }, - { - NewDuplicate("", "value"), - "[1]", - }, - } - for _, testCase := range testCases { - errList := ErrorList{testCase.Err} - prefix := errList.PrefixIndex(1) - if prefix == nil || len(prefix) != len(errList) { - t.Errorf("PrefixIndex should return self") - } - if e, a := testCase.Expected, errList[0].(ValidationError).Field; e != a { - t.Errorf("expected %s, got %s", e, a) - } +func Test_reasonForError(t *testing.T) { + if e, a := api.StatusReasonUnknown, reasonForError(nil); e != a { + t.Errorf("unexpected reason type: %#v", a) } } diff --git a/pkg/api/errors/validation.go b/pkg/api/errors/validation.go new file mode 100644 index 00000000000..6626f0c76bb --- /dev/null +++ b/pkg/api/errors/validation.go @@ -0,0 +1,155 @@ +/* +Copyright 2014 Google Inc. All rights reserved. + +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 errors + +import ( + "fmt" + "strings" + + "github.com/golang/glog" +) + +// ValidationErrorType is a machine readable value providing more detail about why +// a field is invalid. These values are expected to match 1-1 with +// CauseType in api/types.go. +type ValidationErrorType string + +const ( + // ValidationErrorTypeNotFound is used to report failure to find a requested value + // (e.g. looking up an ID). + ValidationErrorTypeNotFound ValidationErrorType = "fieldValueNotFound" + // ValidationErrorTypeRequired is used to report required values that are not + // provided (e.g. empty strings, null values, or empty arrays). + ValidationErrorTypeRequired ValidationErrorType = "fieldValueRequired" + // ValidationErrorTypeDuplicate is used to report collisions of values that must be + // unique (e.g. unique IDs). + ValidationErrorTypeDuplicate ValidationErrorType = "fieldValueDuplicate" + // ValidationErrorTypeInvalid is used to report malformed values (e.g. failed regex + // match). + ValidationErrorTypeInvalid ValidationErrorType = "fieldValueInvalid" + // ValidationErrorTypeNotSupported is used to report valid (as per formatting rules) + // values that can not be handled (e.g. an enumerated string). + ValidationErrorTypeNotSupported ValidationErrorType = "fieldValueNotSupported" +) + +func ValueOf(t ValidationErrorType) string { + switch t { + case ValidationErrorTypeNotFound: + return "not found" + case ValidationErrorTypeRequired: + return "required value" + case ValidationErrorTypeDuplicate: + return "duplicate value" + case ValidationErrorTypeInvalid: + return "invalid value" + case ValidationErrorTypeNotSupported: + return "unsupported value" + default: + glog.Errorf("unrecognized validation type: %#v", t) + return "" + } +} + +// ValidationError is an implementation of the 'error' interface, which represents an error of validation. +type ValidationError struct { + Type ValidationErrorType + Field string + BadValue interface{} +} + +func (v ValidationError) Error() string { + return fmt.Sprintf("%s: %v '%v'", v.Field, ValueOf(v.Type), v.BadValue) +} + +// NewFieldRequired returns a ValidationError indicating "value required" +func NewFieldRequired(field string, value interface{}) ValidationError { + return ValidationError{ValidationErrorTypeRequired, field, value} +} + +// NewFieldInvalid returns a ValidationError indicating "invalid value" +func NewFieldInvalid(field string, value interface{}) ValidationError { + return ValidationError{ValidationErrorTypeInvalid, field, value} +} + +// NewFieldNotSupported returns a ValidationError indicating "unsupported value" +func NewFieldNotSupported(field string, value interface{}) ValidationError { + return ValidationError{ValidationErrorTypeNotSupported, field, value} +} + +// NewFieldDuplicate returns a ValidationError indicating "duplicate value" +func NewFieldDuplicate(field string, value interface{}) ValidationError { + return ValidationError{ValidationErrorTypeDuplicate, field, value} +} + +// NewFieldNotFound returns a ValidationError indicating "value not found" +func NewFieldNotFound(field string, value interface{}) ValidationError { + return ValidationError{ValidationErrorTypeNotFound, field, value} +} + +// ErrorList is a collection of errors. This does not implement the error +// interface to avoid confusion where an empty ErrorList would still be an +// error (non-nil). To produce a single error instance from an ErrorList, use +// the ToError() method, which will return nil for an empty ErrorList. +type ErrorList []error + +// This helper implements the error interface for ErrorList, but prevents +// accidental conversion of ErrorList to error. +type errorListInternal ErrorList + +// Error is part of the error interface. +func (list errorListInternal) Error() string { + if len(list) == 0 { + return "" + } + sl := make([]string, len(list)) + for i := range list { + sl[i] = list[i].Error() + } + return strings.Join(sl, "; ") +} + +// ToError converts an ErrorList into a "normal" error, or nil if the list is empty. +func (list ErrorList) ToError() error { + if len(list) == 0 { + return nil + } + return errorListInternal(list) +} + +// Prefix adds a prefix to the Field of every ValidationError in the list. Returns +// the list for convenience. +func (list ErrorList) Prefix(prefix string) ErrorList { + for i := range list { + if err, ok := list[i].(ValidationError); ok { + if strings.HasPrefix(err.Field, "[") { + err.Field = prefix + err.Field + } else if len(err.Field) != 0 { + err.Field = prefix + "." + err.Field + } else { + err.Field = prefix + } + list[i] = err + } + } + return list +} + +// PrefixIndex adds an index to the Field of every ValidationError in the list. Returns +// the list for convenience. +func (list ErrorList) PrefixIndex(index int) ErrorList { + return list.Prefix(fmt.Sprintf("[%d]", index)) +} diff --git a/pkg/api/errors/validation_test.go b/pkg/api/errors/validation_test.go new file mode 100644 index 00000000000..7e95b308034 --- /dev/null +++ b/pkg/api/errors/validation_test.go @@ -0,0 +1,163 @@ +/* +Copyright 2014 Google Inc. All rights reserved. + +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 errors + +import ( + "fmt" + "strings" + "testing" +) + +func TestMakeFuncs(t *testing.T) { + testCases := []struct { + fn func() ValidationError + expected ValidationErrorType + }{ + { + func() ValidationError { return NewFieldInvalid("f", "v") }, + ValidationErrorTypeInvalid, + }, + { + func() ValidationError { return NewFieldNotSupported("f", "v") }, + ValidationErrorTypeNotSupported, + }, + { + func() ValidationError { return NewFieldDuplicate("f", "v") }, + ValidationErrorTypeDuplicate, + }, + { + func() ValidationError { return NewFieldNotFound("f", "v") }, + ValidationErrorTypeNotFound, + }, + { + func() ValidationError { return NewFieldRequired("f", "v") }, + ValidationErrorTypeRequired, + }, + } + + for _, testCase := range testCases { + err := testCase.fn() + if err.Type != testCase.expected { + t.Errorf("expected Type %q, got %q", testCase.expected, err.Type) + } + } +} + +func TestValidationError(t *testing.T) { + s := NewFieldInvalid("foo", "bar").Error() + if !strings.Contains(s, "foo") || !strings.Contains(s, "bar") || !strings.Contains(s, ValueOf(ValidationErrorTypeInvalid)) { + t.Errorf("error message did not contain expected values, got %s", s) + } +} + +func TestErrorList(t *testing.T) { + errList := ErrorList{} + if a := errList.ToError(); a != nil { + t.Errorf("unexpected non-nil error for empty list: %v", a) + } + if a := errorListInternal(errList).Error(); a != "" { + t.Errorf("expected empty string, got %v", a) + } + errList = append(errList, NewFieldInvalid("field", "value")) + // The fact that this compiles is the test. +} + +func TestErrorListToError(t *testing.T) { + errList := ErrorList{} + err := errList.ToError() + if err != nil { + t.Errorf("expected nil, got %v", err) + } + + testCases := []struct { + errs ErrorList + expected string + }{ + {ErrorList{fmt.Errorf("abc")}, "abc"}, + {ErrorList{fmt.Errorf("abc"), fmt.Errorf("123")}, "abc; 123"}, + } + for _, testCase := range testCases { + err := testCase.errs.ToError() + if err == nil { + t.Errorf("expected an error, got nil: ErrorList=%v", testCase) + continue + } + if err.Error() != testCase.expected { + t.Errorf("expected %q, got %q", testCase.expected, err.Error()) + } + } +} + +func TestErrListPrefix(t *testing.T) { + testCases := []struct { + Err ValidationError + Expected string + }{ + { + NewFieldNotFound("[0].bar", "value"), + "foo[0].bar", + }, + { + NewFieldInvalid("field", "value"), + "foo.field", + }, + { + NewFieldDuplicate("", "value"), + "foo", + }, + } + for _, testCase := range testCases { + errList := ErrorList{testCase.Err} + prefix := errList.Prefix("foo") + if prefix == nil || len(prefix) != len(errList) { + t.Errorf("Prefix should return self") + } + if e, a := testCase.Expected, errList[0].(ValidationError).Field; e != a { + t.Errorf("expected %s, got %s", e, a) + } + } +} + +func TestErrListPrefixIndex(t *testing.T) { + testCases := []struct { + Err ValidationError + Expected string + }{ + { + NewFieldNotFound("[0].bar", "value"), + "[1][0].bar", + }, + { + NewFieldInvalid("field", "value"), + "[1].field", + }, + { + NewFieldDuplicate("", "value"), + "[1]", + }, + } + for _, testCase := range testCases { + errList := ErrorList{testCase.Err} + prefix := errList.PrefixIndex(1) + if prefix == nil || len(prefix) != len(errList) { + t.Errorf("PrefixIndex should return self") + } + if e, a := testCase.Expected, errList[0].(ValidationError).Field; e != a { + t.Errorf("expected %s, got %s", e, a) + } + } +} diff --git a/pkg/api/validation/validation.go b/pkg/api/validation/validation.go index 49bbb8dc7ff..e362699e1e4 100644 --- a/pkg/api/validation/validation.go +++ b/pkg/api/validation/validation.go @@ -37,11 +37,11 @@ func validateVolumes(volumes []api.Volume) (util.StringSet, errs.ErrorList) { el = validateSource(vol.Source).Prefix("source") } if len(vol.Name) == 0 { - el = append(el, errs.NewRequired("name", vol.Name)) + el = append(el, errs.NewFieldRequired("name", vol.Name)) } else if !util.IsDNSLabel(vol.Name) { - el = append(el, errs.NewInvalid("name", vol.Name)) + el = append(el, errs.NewFieldInvalid("name", vol.Name)) } else if allNames.Has(vol.Name) { - el = append(el, errs.NewDuplicate("name", vol.Name)) + el = append(el, errs.NewFieldDuplicate("name", vol.Name)) } if len(el) == 0 { allNames.Insert(vol.Name) @@ -64,7 +64,7 @@ func validateSource(source *api.VolumeSource) errs.ErrorList { //EmptyDirs have nothing to validate } if numVolumes != 1 { - allErrs = append(allErrs, errs.NewInvalid("", source)) + allErrs = append(allErrs, errs.NewFieldInvalid("", source)) } return allErrs } @@ -88,25 +88,25 @@ func validatePorts(ports []api.Port) errs.ErrorList { port := &ports[i] // so we can set default values if len(port.Name) > 0 { if len(port.Name) > 63 || !util.IsDNSLabel(port.Name) { - pErrs = append(pErrs, errs.NewInvalid("name", port.Name)) + pErrs = append(pErrs, errs.NewFieldInvalid("name", port.Name)) } else if allNames.Has(port.Name) { - pErrs = append(pErrs, errs.NewDuplicate("name", port.Name)) + pErrs = append(pErrs, errs.NewFieldDuplicate("name", port.Name)) } else { allNames.Insert(port.Name) } } if port.ContainerPort == 0 { - pErrs = append(pErrs, errs.NewRequired("containerPort", port.ContainerPort)) + pErrs = append(pErrs, errs.NewFieldRequired("containerPort", port.ContainerPort)) } else if !util.IsValidPortNum(port.ContainerPort) { - pErrs = append(pErrs, errs.NewInvalid("containerPort", port.ContainerPort)) + pErrs = append(pErrs, errs.NewFieldInvalid("containerPort", port.ContainerPort)) } if port.HostPort != 0 && !util.IsValidPortNum(port.HostPort) { - pErrs = append(pErrs, errs.NewInvalid("hostPort", port.HostPort)) + pErrs = append(pErrs, errs.NewFieldInvalid("hostPort", port.HostPort)) } if len(port.Protocol) == 0 { port.Protocol = "TCP" } else if !supportedPortProtocols.Has(strings.ToUpper(port.Protocol)) { - pErrs = append(pErrs, errs.NewNotSupported("protocol", port.Protocol)) + pErrs = append(pErrs, errs.NewFieldNotSupported("protocol", port.Protocol)) } allErrs = append(allErrs, pErrs.PrefixIndex(i)...) } @@ -120,10 +120,10 @@ func validateEnv(vars []api.EnvVar) errs.ErrorList { vErrs := errs.ErrorList{} ev := &vars[i] // so we can set default values if len(ev.Name) == 0 { - vErrs = append(vErrs, errs.NewRequired("name", ev.Name)) + vErrs = append(vErrs, errs.NewFieldRequired("name", ev.Name)) } if !util.IsCIdentifier(ev.Name) { - vErrs = append(vErrs, errs.NewInvalid("name", ev.Name)) + vErrs = append(vErrs, errs.NewFieldInvalid("name", ev.Name)) } allErrs = append(allErrs, vErrs.PrefixIndex(i)...) } @@ -137,12 +137,12 @@ func validateVolumeMounts(mounts []api.VolumeMount, volumes util.StringSet) errs mErrs := errs.ErrorList{} mnt := &mounts[i] // so we can set default values if len(mnt.Name) == 0 { - mErrs = append(mErrs, errs.NewRequired("name", mnt.Name)) + mErrs = append(mErrs, errs.NewFieldRequired("name", mnt.Name)) } else if !volumes.Has(mnt.Name) { mErrs = append(mErrs, errs.NewNotFound("name", mnt.Name)) } if len(mnt.MountPath) == 0 { - mErrs = append(mErrs, errs.NewRequired("mountPath", mnt.MountPath)) + mErrs = append(mErrs, errs.NewFieldRequired("mountPath", mnt.MountPath)) } allErrs = append(allErrs, mErrs.PrefixIndex(i)...) } @@ -163,7 +163,7 @@ func AccumulateUniquePorts(containers []api.Container, accumulator map[int]bool, continue } if accumulator[port] { - cErrs = append(cErrs, errs.NewDuplicate("Port", port)) + cErrs = append(cErrs, errs.NewFieldDuplicate("Port", port)) } else { accumulator[port] = true } @@ -188,16 +188,16 @@ func validateContainers(containers []api.Container, volumes util.StringSet) errs cErrs := errs.ErrorList{} ctr := &containers[i] // so we can set default values if len(ctr.Name) == 0 { - cErrs = append(cErrs, errs.NewRequired("name", ctr.Name)) + cErrs = append(cErrs, errs.NewFieldRequired("name", ctr.Name)) } else if !util.IsDNSLabel(ctr.Name) { - cErrs = append(cErrs, errs.NewInvalid("name", ctr.Name)) + cErrs = append(cErrs, errs.NewFieldInvalid("name", ctr.Name)) } else if allNames.Has(ctr.Name) { - cErrs = append(cErrs, errs.NewDuplicate("name", ctr.Name)) + cErrs = append(cErrs, errs.NewFieldDuplicate("name", ctr.Name)) } else { allNames.Insert(ctr.Name) } if len(ctr.Image) == 0 { - cErrs = append(cErrs, errs.NewRequired("image", ctr.Image)) + cErrs = append(cErrs, errs.NewFieldRequired("image", ctr.Image)) } cErrs = append(cErrs, validatePorts(ctr.Ports).Prefix("ports")...) cErrs = append(cErrs, validateEnv(ctr.Env).Prefix("env")...) @@ -224,9 +224,9 @@ func ValidateManifest(manifest *api.ContainerManifest) errs.ErrorList { allErrs := errs.ErrorList{} if len(manifest.Version) == 0 { - allErrs = append(allErrs, errs.NewRequired("version", manifest.Version)) + allErrs = append(allErrs, errs.NewFieldRequired("version", manifest.Version)) } else if !supportedManifestVersions.Has(strings.ToLower(manifest.Version)) { - allErrs = append(allErrs, errs.NewNotSupported("version", manifest.Version)) + allErrs = append(allErrs, errs.NewFieldNotSupported("version", manifest.Version)) } allVolumes, errs := validateVolumes(manifest.Volumes) allErrs = append(allErrs, errs.Prefix("volumes")...) @@ -241,7 +241,7 @@ func ValidatePodState(podState *api.PodState) errs.ErrorList { } else if podState.RestartPolicy.Type != api.RestartAlways && podState.RestartPolicy.Type != api.RestartOnFailure && podState.RestartPolicy.Type != api.RestartNever { - allErrs = append(allErrs, errs.NewNotSupported("restartPolicy.type", podState.RestartPolicy.Type)) + allErrs = append(allErrs, errs.NewFieldNotSupported("restartPolicy.type", podState.RestartPolicy.Type)) } return allErrs @@ -251,7 +251,7 @@ func ValidatePodState(podState *api.PodState) errs.ErrorList { func ValidatePod(pod *api.Pod) errs.ErrorList { allErrs := errs.ErrorList{} if len(pod.ID) == 0 { - allErrs = append(allErrs, errs.NewRequired("id", pod.ID)) + allErrs = append(allErrs, errs.NewFieldRequired("id", pod.ID)) } allErrs = append(allErrs, ValidatePodState(&pod.DesiredState).Prefix("desiredState")...) return allErrs @@ -261,15 +261,15 @@ func ValidatePod(pod *api.Pod) errs.ErrorList { func ValidateService(service *api.Service) errs.ErrorList { allErrs := errs.ErrorList{} if len(service.ID) == 0 { - allErrs = append(allErrs, errs.NewRequired("id", service.ID)) + allErrs = append(allErrs, errs.NewFieldRequired("id", service.ID)) } else if !util.IsDNS952Label(service.ID) { - allErrs = append(allErrs, errs.NewInvalid("id", service.ID)) + allErrs = append(allErrs, errs.NewFieldInvalid("id", service.ID)) } if !util.IsValidPortNum(service.Port) { - allErrs = append(allErrs, errs.NewInvalid("Service.Port", service.Port)) + allErrs = append(allErrs, errs.NewFieldInvalid("Service.Port", service.Port)) } if labels.Set(service.Selector).AsSelector().Empty() { - allErrs = append(allErrs, errs.NewRequired("selector", service.Selector)) + allErrs = append(allErrs, errs.NewFieldRequired("selector", service.Selector)) } return allErrs } @@ -278,18 +278,18 @@ func ValidateService(service *api.Service) errs.ErrorList { func ValidateReplicationController(controller *api.ReplicationController) errs.ErrorList { allErrs := errs.ErrorList{} if len(controller.ID) == 0 { - allErrs = append(allErrs, errs.NewRequired("id", controller.ID)) + allErrs = append(allErrs, errs.NewFieldRequired("id", controller.ID)) } if labels.Set(controller.DesiredState.ReplicaSelector).AsSelector().Empty() { - allErrs = append(allErrs, errs.NewRequired("desiredState.replicaSelector", controller.DesiredState.ReplicaSelector)) + allErrs = append(allErrs, errs.NewFieldRequired("desiredState.replicaSelector", controller.DesiredState.ReplicaSelector)) } selector := labels.Set(controller.DesiredState.ReplicaSelector).AsSelector() labels := labels.Set(controller.DesiredState.PodTemplate.Labels) if !selector.Matches(labels) { - allErrs = append(allErrs, errs.NewInvalid("desiredState.podTemplate.labels", controller.DesiredState.PodTemplate)) + allErrs = append(allErrs, errs.NewFieldInvalid("desiredState.podTemplate.labels", controller.DesiredState.PodTemplate)) } if controller.DesiredState.Replicas < 0 { - allErrs = append(allErrs, errs.NewInvalid("desiredState.replicas", controller.DesiredState.Replicas)) + allErrs = append(allErrs, errs.NewFieldInvalid("desiredState.replicas", controller.DesiredState.Replicas)) } allErrs = append(allErrs, ValidateManifest(&controller.DesiredState.PodTemplate.DesiredState.Manifest).Prefix("desiredState.podTemplate.desiredState.manifest")...) return allErrs diff --git a/pkg/apiserver/apiserver_test.go b/pkg/apiserver/apiserver_test.go index 5d6789bf740..75bbbab51ab 100644 --- a/pkg/apiserver/apiserver_test.go +++ b/pkg/apiserver/apiserver_test.go @@ -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, diff --git a/pkg/apiserver/errors.go b/pkg/apiserver/errors.go index 54384544b60..5172ef303b9 100644 --- a/pkg/apiserver/errors.go +++ b/pkg/apiserver/errors.go @@ -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 diff --git a/pkg/apiserver/errors_test.go b/pkg/apiserver/errors_test.go index d887faa9874..13b3ae4ad83 100644 --- a/pkg/apiserver/errors_test.go +++ b/pkg/apiserver/errors_test.go @@ -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) + } } } diff --git a/pkg/kubelet/config/config.go b/pkg/kubelet/config/config.go index 4f2ce2c1f1c..3f38067db2b 100644 --- a/pkg/kubelet/config/config.go +++ b/pkg/kubelet/config/config.go @@ -251,7 +251,7 @@ func filterInvalidPods(pods []kubelet.Pod, source string) (filtered []*kubelet.P for i := range pods { var errors []error if names.Has(pods[i].Name) { - errors = append(errors, apierrs.NewDuplicate("Pod.Name", pods[i].Name)) + errors = append(errors, apierrs.NewFieldDuplicate("name", pods[i].Name)) } else { names.Insert(pods[i].Name) } diff --git a/pkg/kubelet/validation.go b/pkg/kubelet/validation.go index 74b0b90a9d4..6cf3cfbada9 100644 --- a/pkg/kubelet/validation.go +++ b/pkg/kubelet/validation.go @@ -24,7 +24,7 @@ import ( func ValidatePod(pod *Pod) (errors []error) { if !util.IsDNSSubdomain(pod.Name) { - errors = append(errors, apierrs.NewInvalid("Pod.Name", pod.Name)) + errors = append(errors, apierrs.NewFieldInvalid("name", pod.Name)) } if errs := validation.ValidateManifest(&pod.Manifest); len(errs) != 0 { errors = append(errors, errs...) diff --git a/pkg/registry/binding/storage.go b/pkg/registry/binding/storage.go index 6f320f95744..c6aeef564c6 100644 --- a/pkg/registry/binding/storage.go +++ b/pkg/registry/binding/storage.go @@ -20,6 +20,7 @@ import ( "fmt" "github.com/GoogleCloudPlatform/kubernetes/pkg/api" + "github.com/GoogleCloudPlatform/kubernetes/pkg/api/errors" "github.com/GoogleCloudPlatform/kubernetes/pkg/apiserver" "github.com/GoogleCloudPlatform/kubernetes/pkg/labels" ) @@ -40,17 +41,17 @@ func NewBindingStorage(bindingRegistry Registry) *BindingStorage { // List returns an error because bindings are write-only objects. func (*BindingStorage) List(selector labels.Selector) (interface{}, error) { - return nil, apiserver.NewNotFoundErr("binding", "list") + return nil, errors.NewNotFound("binding", "list") } // Get returns an error because bindings are write-only objects. func (*BindingStorage) Get(id string) (interface{}, error) { - return nil, apiserver.NewNotFoundErr("binding", id) + return nil, errors.NewNotFound("binding", id) } // Delete returns an error because bindings are write-only objects. func (*BindingStorage) Delete(id string) (<-chan interface{}, error) { - return nil, apiserver.NewNotFoundErr("binding", id) + return nil, errors.NewNotFound("binding", id) } // New returns a new binding object fit for having data unmarshalled into it. diff --git a/pkg/registry/controller/storage.go b/pkg/registry/controller/storage.go index 4eafbbef61c..8ce5e5fe469 100644 --- a/pkg/registry/controller/storage.go +++ b/pkg/registry/controller/storage.go @@ -21,6 +21,7 @@ import ( "time" "github.com/GoogleCloudPlatform/kubernetes/pkg/api" + "github.com/GoogleCloudPlatform/kubernetes/pkg/api/errors" "github.com/GoogleCloudPlatform/kubernetes/pkg/api/validation" "github.com/GoogleCloudPlatform/kubernetes/pkg/apiserver" "github.com/GoogleCloudPlatform/kubernetes/pkg/labels" @@ -61,7 +62,7 @@ func (rs *RegistryStorage) Create(obj interface{}) (<-chan interface{}, error) { // Pod Manifest ID should be assigned by the pod API controller.DesiredState.PodTemplate.DesiredState.Manifest.ID = "" if errs := validation.ValidateReplicationController(controller); len(errs) > 0 { - return nil, apiserver.NewInvalidErr("replicationController", controller.ID, errs) + return nil, errors.NewInvalid("replicationController", controller.ID, errs) } controller.CreationTimestamp = util.Now() @@ -120,7 +121,7 @@ func (rs *RegistryStorage) Update(obj interface{}) (<-chan interface{}, error) { return nil, fmt.Errorf("not a replication controller: %#v", obj) } if errs := validation.ValidateReplicationController(controller); len(errs) > 0 { - return nil, apiserver.NewInvalidErr("replicationController", controller.ID, errs) + return nil, errors.NewInvalid("replicationController", controller.ID, errs) } return apiserver.MakeAsync(func() (interface{}, error) { err := rs.registry.UpdateController(*controller) diff --git a/pkg/registry/controller/storage_test.go b/pkg/registry/controller/storage_test.go index 6bd157e3331..d9514031d3b 100644 --- a/pkg/registry/controller/storage_test.go +++ b/pkg/registry/controller/storage_test.go @@ -25,7 +25,7 @@ import ( "time" "github.com/GoogleCloudPlatform/kubernetes/pkg/api" - "github.com/GoogleCloudPlatform/kubernetes/pkg/apiserver" + "github.com/GoogleCloudPlatform/kubernetes/pkg/api/errors" "github.com/GoogleCloudPlatform/kubernetes/pkg/labels" "github.com/GoogleCloudPlatform/kubernetes/pkg/registry/registrytest" "github.com/GoogleCloudPlatform/kubernetes/pkg/runtime" @@ -280,7 +280,7 @@ func TestControllerStorageValidatesCreate(t *testing.T) { if c != nil { t.Errorf("Expected nil channel") } - if !apiserver.IsInvalid(err) { + if !errors.IsInvalid(err) { t.Errorf("Expected to get an invalid resource error, got %v", err) } } @@ -310,7 +310,7 @@ func TestControllerStorageValidatesUpdate(t *testing.T) { if c != nil { t.Errorf("Expected nil channel") } - if !apiserver.IsInvalid(err) { + if !errors.IsInvalid(err) { t.Errorf("Expected to get an invalid resource error, got %v", err) } } diff --git a/pkg/registry/endpoint/storage_test.go b/pkg/registry/endpoint/storage_test.go index e57c8e25958..06a7305270e 100644 --- a/pkg/registry/endpoint/storage_test.go +++ b/pkg/registry/endpoint/storage_test.go @@ -21,7 +21,7 @@ import ( "testing" "github.com/GoogleCloudPlatform/kubernetes/pkg/api" - "github.com/GoogleCloudPlatform/kubernetes/pkg/apiserver" + "github.com/GoogleCloudPlatform/kubernetes/pkg/api/errors" "github.com/GoogleCloudPlatform/kubernetes/pkg/labels" "github.com/GoogleCloudPlatform/kubernetes/pkg/registry/registrytest" ) @@ -45,13 +45,13 @@ func TestGetEndpoints(t *testing.T) { func TestGetEndpointsMissingService(t *testing.T) { registry := ®istrytest.ServiceRegistry{ - Err: apiserver.NewNotFoundErr("service", "foo"), + Err: errors.NewNotFound("service", "foo"), } storage := NewStorage(registry) // returns service not found _, err := storage.Get("foo") - if !apiserver.IsNotFound(err) || !reflect.DeepEqual(err, apiserver.NewNotFoundErr("service", "foo")) { + if !errors.IsNotFound(err) || !reflect.DeepEqual(err, errors.NewNotFound("service", "foo")) { t.Errorf("expected NotFound error, got %#v", err) } diff --git a/pkg/registry/etcd/etcd.go b/pkg/registry/etcd/etcd.go index b458e73f6cd..38dd5f5e48c 100644 --- a/pkg/registry/etcd/etcd.go +++ b/pkg/registry/etcd/etcd.go @@ -20,7 +20,7 @@ import ( "fmt" "github.com/GoogleCloudPlatform/kubernetes/pkg/api" - "github.com/GoogleCloudPlatform/kubernetes/pkg/apiserver" + "github.com/GoogleCloudPlatform/kubernetes/pkg/api/errors" "github.com/GoogleCloudPlatform/kubernetes/pkg/constraint" "github.com/GoogleCloudPlatform/kubernetes/pkg/labels" "github.com/GoogleCloudPlatform/kubernetes/pkg/runtime" @@ -184,7 +184,7 @@ func (r *Registry) DeletePod(podID string) error { podKey := makePodKey(podID) err := r.ExtractObj(podKey, &pod, false) if tools.IsEtcdNotFound(err) { - return apiserver.NewNotFoundErr("pod", podID) + return errors.NewNotFound("pod", podID) } if err != nil { return err @@ -193,7 +193,7 @@ func (r *Registry) DeletePod(podID string) error { // machine and attempt to put it somewhere. err = r.Delete(podKey, true) if tools.IsEtcdNotFound(err) { - return apiserver.NewNotFoundErr("pod", podID) + return errors.NewNotFound("pod", podID) } if err != nil { return err @@ -249,7 +249,7 @@ func (r *Registry) GetController(controllerID string) (*api.ReplicationControlle key := makeControllerKey(controllerID) err := r.ExtractObj(key, &controller, false) if tools.IsEtcdNotFound(err) { - return nil, apiserver.NewNotFoundErr("replicationController", controllerID) + return nil, errors.NewNotFound("replicationController", controllerID) } if err != nil { return nil, err @@ -261,7 +261,7 @@ func (r *Registry) GetController(controllerID string) (*api.ReplicationControlle func (r *Registry) CreateController(controller api.ReplicationController) error { err := r.CreateObj(makeControllerKey(controller.ID), controller) if tools.IsEtcdNodeExist(err) { - return apiserver.NewAlreadyExistsErr("replicationController", controller.ID) + return errors.NewAlreadyExists("replicationController", controller.ID) } return err } @@ -276,7 +276,7 @@ func (r *Registry) DeleteController(controllerID string) error { key := makeControllerKey(controllerID) err := r.Delete(key, false) if tools.IsEtcdNotFound(err) { - return apiserver.NewNotFoundErr("replicationController", controllerID) + return errors.NewNotFound("replicationController", controllerID) } return err } @@ -296,7 +296,7 @@ func (r *Registry) ListServices() (*api.ServiceList, error) { func (r *Registry) CreateService(svc api.Service) error { err := r.CreateObj(makeServiceKey(svc.ID), svc) if tools.IsEtcdNodeExist(err) { - return apiserver.NewAlreadyExistsErr("service", svc.ID) + return errors.NewAlreadyExists("service", svc.ID) } return err } @@ -307,7 +307,7 @@ func (r *Registry) GetService(name string) (*api.Service, error) { var svc api.Service err := r.ExtractObj(key, &svc, false) if tools.IsEtcdNotFound(err) { - return nil, apiserver.NewNotFoundErr("service", name) + return nil, errors.NewNotFound("service", name) } if err != nil { return nil, err @@ -321,7 +321,7 @@ func (r *Registry) GetEndpoints(name string) (*api.Endpoints, error) { var endpoints api.Endpoints err := r.ExtractObj(key, &endpoints, false) if tools.IsEtcdNotFound(err) { - return nil, apiserver.NewNotFoundErr("endpoints", name) + return nil, errors.NewNotFound("endpoints", name) } if err != nil { return nil, err @@ -338,7 +338,7 @@ func (r *Registry) DeleteService(name string) error { key := makeServiceKey(name) err := r.Delete(key, true) if tools.IsEtcdNotFound(err) { - return apiserver.NewNotFoundErr("service", name) + return errors.NewNotFound("service", name) } if err != nil { return err diff --git a/pkg/registry/etcd/etcd_test.go b/pkg/registry/etcd/etcd_test.go index 2ad142c5081..cae9740b6c3 100644 --- a/pkg/registry/etcd/etcd_test.go +++ b/pkg/registry/etcd/etcd_test.go @@ -21,8 +21,8 @@ import ( "testing" "github.com/GoogleCloudPlatform/kubernetes/pkg/api" + "github.com/GoogleCloudPlatform/kubernetes/pkg/api/errors" _ "github.com/GoogleCloudPlatform/kubernetes/pkg/api/v1beta1" - "github.com/GoogleCloudPlatform/kubernetes/pkg/apiserver" "github.com/GoogleCloudPlatform/kubernetes/pkg/labels" "github.com/GoogleCloudPlatform/kubernetes/pkg/registry/registrytest" "github.com/GoogleCloudPlatform/kubernetes/pkg/runtime" @@ -627,7 +627,7 @@ func TestEtcdCreateControllerAlreadyExisting(t *testing.T) { ID: "foo", }, }) - if !apiserver.IsAlreadyExists(err) { + if !errors.IsAlreadyExists(err) { t.Errorf("expected already exists err, got %#v", err) } } @@ -716,7 +716,7 @@ func TestEtcdCreateServiceAlreadyExisting(t *testing.T) { err := registry.CreateService(api.Service{ JSONBase: api.JSONBase{ID: "foo"}, }) - if !apiserver.IsAlreadyExists(err) { + if !errors.IsAlreadyExists(err) { t.Errorf("expected already exists err, got %#v", err) } } diff --git a/pkg/registry/pod/storage.go b/pkg/registry/pod/storage.go index a3ee524ee44..730351067f4 100644 --- a/pkg/registry/pod/storage.go +++ b/pkg/registry/pod/storage.go @@ -23,6 +23,7 @@ import ( "time" "github.com/GoogleCloudPlatform/kubernetes/pkg/api" + "github.com/GoogleCloudPlatform/kubernetes/pkg/api/errors" "github.com/GoogleCloudPlatform/kubernetes/pkg/api/validation" "github.com/GoogleCloudPlatform/kubernetes/pkg/apiserver" "github.com/GoogleCloudPlatform/kubernetes/pkg/client" @@ -70,7 +71,7 @@ func (rs *RegistryStorage) Create(obj interface{}) (<-chan interface{}, error) { } pod.DesiredState.Manifest.ID = pod.ID if errs := validation.ValidatePod(pod); len(errs) > 0 { - return nil, apiserver.NewInvalidErr("pod", pod.ID, errs) + return nil, errors.NewInvalid("pod", pod.ID, errs) } pod.CreationTimestamp = util.Now() @@ -137,7 +138,7 @@ func (rs RegistryStorage) New() interface{} { func (rs *RegistryStorage) Update(obj interface{}) (<-chan interface{}, error) { pod := obj.(*api.Pod) if errs := validation.ValidatePod(pod); len(errs) > 0 { - return nil, apiserver.NewInvalidErr("pod", pod.ID, errs) + return nil, errors.NewInvalid("pod", pod.ID, errs) } return apiserver.MakeAsync(func() (interface{}, error) { if err := rs.registry.UpdatePod(*pod); err != nil { diff --git a/pkg/registry/pod/storage_test.go b/pkg/registry/pod/storage_test.go index 7268306788b..424333bb0a1 100644 --- a/pkg/registry/pod/storage_test.go +++ b/pkg/registry/pod/storage_test.go @@ -23,7 +23,7 @@ import ( "time" "github.com/GoogleCloudPlatform/kubernetes/pkg/api" - "github.com/GoogleCloudPlatform/kubernetes/pkg/apiserver" + "github.com/GoogleCloudPlatform/kubernetes/pkg/api/errors" "github.com/GoogleCloudPlatform/kubernetes/pkg/cloudprovider/fake" "github.com/GoogleCloudPlatform/kubernetes/pkg/labels" "github.com/GoogleCloudPlatform/kubernetes/pkg/registry/registrytest" @@ -337,7 +337,7 @@ func TestPodStorageValidatesCreate(t *testing.T) { if c != nil { t.Errorf("Expected nil channel") } - if !apiserver.IsInvalid(err) { + if !errors.IsInvalid(err) { t.Errorf("Expected to get an invalid resource error, got %v", err) } } @@ -353,7 +353,7 @@ func TestPodStorageValidatesUpdate(t *testing.T) { if c != nil { t.Errorf("Expected nil channel") } - if !apiserver.IsInvalid(err) { + if !errors.IsInvalid(err) { t.Errorf("Expected to get an invalid resource error, got %v", err) } } diff --git a/pkg/registry/service/storage.go b/pkg/registry/service/storage.go index 742847c4b4b..44da947b49c 100644 --- a/pkg/registry/service/storage.go +++ b/pkg/registry/service/storage.go @@ -23,6 +23,7 @@ import ( "strings" "github.com/GoogleCloudPlatform/kubernetes/pkg/api" + "github.com/GoogleCloudPlatform/kubernetes/pkg/api/errors" "github.com/GoogleCloudPlatform/kubernetes/pkg/api/validation" "github.com/GoogleCloudPlatform/kubernetes/pkg/apiserver" "github.com/GoogleCloudPlatform/kubernetes/pkg/cloudprovider" @@ -51,7 +52,7 @@ func NewRegistryStorage(registry Registry, cloud cloudprovider.Interface, machin func (rs *RegistryStorage) Create(obj interface{}) (<-chan interface{}, error) { srv := obj.(*api.Service) if errs := validation.ValidateService(srv); len(errs) > 0 { - return nil, apiserver.NewInvalidErr("service", srv.ID, errs) + return nil, errors.NewInvalid("service", srv.ID, errs) } srv.CreationTimestamp = util.Now() @@ -157,7 +158,7 @@ func GetServiceEnvironmentVariables(registry Registry, machine string) ([]api.En func (rs *RegistryStorage) Update(obj interface{}) (<-chan interface{}, error) { srv := obj.(*api.Service) if errs := validation.ValidateService(srv); len(errs) > 0 { - return nil, apiserver.NewInvalidErr("service", srv.ID, errs) + return nil, errors.NewInvalid("service", srv.ID, errs) } return apiserver.MakeAsync(func() (interface{}, error) { // TODO: check to see if external load balancer status changed diff --git a/pkg/registry/service/storage_test.go b/pkg/registry/service/storage_test.go index 0186a4635f0..bddfc782cda 100644 --- a/pkg/registry/service/storage_test.go +++ b/pkg/registry/service/storage_test.go @@ -21,6 +21,7 @@ import ( "testing" "github.com/GoogleCloudPlatform/kubernetes/pkg/api" + "github.com/GoogleCloudPlatform/kubernetes/pkg/api/errors" "github.com/GoogleCloudPlatform/kubernetes/pkg/apiserver" cloud "github.com/GoogleCloudPlatform/kubernetes/pkg/cloudprovider/fake" "github.com/GoogleCloudPlatform/kubernetes/pkg/labels" @@ -79,7 +80,7 @@ func TestServiceStorageValidatesCreate(t *testing.T) { if c != nil { t.Errorf("Expected nil channel") } - if !apiserver.IsInvalid(err) { + if !errors.IsInvalid(err) { t.Errorf("Expected to get an invalid resource error, got %v", err) } @@ -140,7 +141,7 @@ func TestServiceStorageValidatesUpdate(t *testing.T) { if c != nil { t.Errorf("Expected nil channel") } - if !apiserver.IsInvalid(err) { + if !errors.IsInvalid(err) { t.Errorf("Expected to get an invalid resource error, got %v", err) } }