Add errdefs package

Providers should use this package so the virtual kubelet core
controllers can understand the errors produced from the provider code.
This commit is contained in:
Brian Goff
2019-05-20 13:39:50 -07:00
parent 8340407f98
commit b9711abff3
7 changed files with 308 additions and 0 deletions

65
errdefs/invalid.go Normal file
View File

@@ -0,0 +1,65 @@
package errdefs
import (
"errors"
"fmt"
)
// InvalidInput is an error interface which denotes whether the opration failed due
// to a the resource not being found.
type ErrInvalidInput interface {
InvalidInput() bool
error
}
type invalidInputError struct {
error
}
func (e *invalidInputError) InvalidInput() bool {
return true
}
func (e *invalidInputError) Cause() error {
return e.error
}
// AsInvalidInput wraps the passed in error to make it of type ErrInvalidInput
//
// Callers should make sure the passed in error has exactly the error message
// it wants as this function does not decorate the message.
func AsInvalidInput(err error) error {
if err == nil {
return nil
}
return &invalidInputError{err}
}
// InvalidInput makes an ErrInvalidInput from the provided error message
func InvalidInput(msg string) error {
return &invalidInputError{errors.New(msg)}
}
// InvalidInputf makes an ErrInvalidInput from the provided error format and args
func InvalidInputf(format string, args ...interface{}) error {
return &invalidInputError{fmt.Errorf(format, args...)}
}
// IsInvalidInput determines if the passed in error is of type ErrInvalidInput
//
// This will traverse the causal chain (`Cause() error`), until it finds an error
// which implements the `InvalidInput` interface.
func IsInvalidInput(err error) bool {
if err == nil {
return false
}
if e, ok := err.(ErrInvalidInput); ok {
return e.InvalidInput()
}
if e, ok := err.(causal); ok {
return IsInvalidInput(e)
}
return false
}

80
errdefs/invalid_test.go Normal file
View File

@@ -0,0 +1,80 @@
package errdefs
import (
"errors"
"fmt"
"testing"
"gotest.tools/assert"
"gotest.tools/assert/cmp"
)
type testingInvalidInputError bool
func (e testingInvalidInputError) Error() string {
return fmt.Sprintf("%v", bool(e))
}
func (e testingInvalidInputError) InvalidInput() bool {
return bool(e)
}
func TestIsInvalidInput(t *testing.T) {
type testCase struct {
name string
err error
xMsg string
xInvalidInput bool
}
for _, c := range []testCase{
{
name: "InvalidInputf",
err: InvalidInputf("%s not found", "foo"),
xMsg: "foo not found",
xInvalidInput: true,
},
{
name: "AsInvalidInput",
err: AsInvalidInput(errors.New("this is a test")),
xMsg: "this is a test",
xInvalidInput: true,
},
{
name: "AsInvalidInputWithNil",
err: AsInvalidInput(nil),
xMsg: "",
xInvalidInput: false,
},
{
name: "nilError",
err: nil,
xMsg: "",
xInvalidInput: false,
},
{
name: "customInvalidInputFalse",
err: testingInvalidInputError(false),
xMsg: "false",
xInvalidInput: false,
},
{
name: "customInvalidInputTrue",
err: testingInvalidInputError(true),
xMsg: "true",
xInvalidInput: true,
},
} {
t.Run(c.name, func(t *testing.T) {
assert.Check(t, cmp.Equal(IsInvalidInput(c.err), c.xInvalidInput))
if c.err != nil {
assert.Check(t, cmp.Equal(c.err.Error(), c.xMsg))
}
})
}
}
func TestInvalidInputCause(t *testing.T) {
err := errors.New("test")
assert.Equal(t, (&invalidInputError{err}).Cause(), err)
}

65
errdefs/notfound.go Normal file
View File

@@ -0,0 +1,65 @@
package errdefs
import (
"errors"
"fmt"
)
// NotFound is an error interface which denotes whether the opration failed due
// to a the resource not being found.
type ErrNotFound interface {
NotFound() bool
error
}
type notFoundError struct {
error
}
func (e *notFoundError) NotFound() bool {
return true
}
func (e *notFoundError) Cause() error {
return e.error
}
// AsNotFound wraps the passed in error to make it of type ErrNotFound
//
// Callers should make sure the passed in error has exactly the error message
// it wants as this function does not decorate the message.
func AsNotFound(err error) error {
if err == nil {
return nil
}
return &notFoundError{err}
}
// NotFound makes an ErrNotFound from the provided error message
func NotFound(msg string) error {
return &notFoundError{errors.New(msg)}
}
// NotFoundf makes an ErrNotFound from the provided error format and args
func NotFoundf(format string, args ...interface{}) error {
return &notFoundError{fmt.Errorf(format, args...)}
}
// IsNotFound determines if the passed in error is of type ErrNotFound
//
// This will traverse the causal chain (`Cause() error`), until it finds an error
// which implements the `NotFound` interface.
func IsNotFound(err error) bool {
if err == nil {
return false
}
if e, ok := err.(ErrNotFound); ok {
return e.NotFound()
}
if e, ok := err.(causal); ok {
return IsNotFound(e)
}
return false
}

80
errdefs/notfound_test.go Normal file
View File

@@ -0,0 +1,80 @@
package errdefs
import (
"errors"
"fmt"
"testing"
"gotest.tools/assert"
"gotest.tools/assert/cmp"
)
type testingNotFoundError bool
func (e testingNotFoundError) Error() string {
return fmt.Sprintf("%v", bool(e))
}
func (e testingNotFoundError) NotFound() bool {
return bool(e)
}
func TestIsNotFound(t *testing.T) {
type testCase struct {
name string
err error
xMsg string
xNotFound bool
}
for _, c := range []testCase{
{
name: "NotFoundf",
err: NotFoundf("%s not found", "foo"),
xMsg: "foo not found",
xNotFound: true,
},
{
name: "AsNotFound",
err: AsNotFound(errors.New("this is a test")),
xMsg: "this is a test",
xNotFound: true,
},
{
name: "AsNotFoundWithNil",
err: AsNotFound(nil),
xMsg: "",
xNotFound: false,
},
{
name: "nilError",
err: nil,
xMsg: "",
xNotFound: false,
},
{
name: "customNotFoundFalse",
err: testingNotFoundError(false),
xMsg: "false",
xNotFound: false,
},
{
name: "customNotFoundTrue",
err: testingNotFoundError(true),
xMsg: "true",
xNotFound: true,
},
} {
t.Run(c.name, func(t *testing.T) {
assert.Check(t, cmp.Equal(IsNotFound(c.err), c.xNotFound))
if c.err != nil {
assert.Check(t, cmp.Equal(c.err.Error(), c.xMsg))
}
})
}
}
func TestNotFoundCause(t *testing.T) {
err := errors.New("test")
assert.Equal(t, (&notFoundError{err}).Cause(), err)
}

10
errdefs/wrapped.go Normal file
View File

@@ -0,0 +1,10 @@
package errdefs
// Causal is an error interface for errors which have wrapped another error
// in a non-opaque way.
//
// This pattern is used by github.com/pkg/errors
type causal interface {
Cause() error
error
}

View File

@@ -11,6 +11,10 @@ import (
)
// Provider contains the methods required to implement a virtual-kubelet provider.
//
// Errors produced by these methods should implement an interface from
// github.com/virtual-kubelet/virtual-kubelet/errdefs package in order for the
// core logic to be able to understand the type of failure.
type Provider interface {
vkubelet.PodLifecycleHandler

View File

@@ -41,6 +41,10 @@ import (
// PodLifecycleHandler defines the interface used by the PodController to react
// to new and changed pods scheduled to the node that is being managed.
//
// Errors produced by these methods should implement an interface from
// github.com/virtual-kubelet/virtual-kubelet/errdefs package in order for the
// core logic to be able to understand the type of failure.
type PodLifecycleHandler interface {
// CreatePod takes a Kubernetes Pod and deploys it within the provider.
CreatePod(ctx context.Context, pod *corev1.Pod) error