Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

improved create service error message #312

Merged
merged 7 commits into from
Aug 15, 2019
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions pkg/errors/errors.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package errors

import (
"fmt"
"strings"
)

func newInvalidCRD(apiGroup string) *KNError {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

decided to make to more generic to be used for more types if needed

parts := strings.Split(apiGroup, ".")
Copy link
Contributor Author

@odra odra Aug 2, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would like some feedback on this approach, I am using the first item in the resource group as the "api name" - both serving and revision return serving

name := parts[0]
msg := fmt.Sprintf("no Knative %s API found on the backend. Please verify the installation.", name)

return NewKNError(msg)
}
18 changes: 18 additions & 0 deletions pkg/errors/errors_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package errors

import (
"gotest.tools/assert"
"testing"
)

func TestNewInvalidCRD(t *testing.T) {
err := newInvalidCRD("serving.knative.dev")
assert.Error(t, err, "no Knative serving API found on the backend. Please verify the installation.")

err = newInvalidCRD("serving")
Copy link
Contributor

@maximilien maximilien Aug 7, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But “serving” would be a valid API group name, no? Why not use something like “fake-serving”?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this function just creates an error struct based on the api group string that is sent as argument - those tests verifies if the api name was properly parsed.

assert.Error(t, err, "no Knative serving API found on the backend. Please verify the installation.")

err = newInvalidCRD("")
assert.Error(t, err, "no Knative API found on the backend. Please verify the installation.")

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why the last extra line?

}
34 changes: 34 additions & 0 deletions pkg/errors/factory.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package errors

import (
api_errors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/apis/meta/v1"
"strings"
)

func isCRDError(status api_errors.APIStatus) bool {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added it here since we only have one for now

for _, cause := range status.Status().Details.Causes {
if strings.HasPrefix(cause.Message, "404") && cause.Type == v1.CauseTypeUnexpectedServerResponse {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I inspected the APIStatus error returned by kubernetes and saw that it returned a 404 error within the causes array, this does not happen in case it is a usual not found error.

return true
}
}

return false
}

func Build(err error) error {
Copy link
Contributor Author

@odra odra Aug 2, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I created a error package to deal with this, it returns a valid error interface + the APIStatus object as well.

It returns the original error in case of a casting error or if the error could not be identified by the error package

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

“Build” seems a misnomer for this function? Also it’s public so add a comment.

apiStatus, ok := err.(api_errors.APIStatus)
if !ok {
return err
}

var knerr *KNError
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

New line not needed...


if isCRDError(apiStatus) {
knerr = newInvalidCRD(apiStatus.Status().Details.Group)
knerr.Status = apiStatus
return knerr
}

return err
}
43 changes: 43 additions & 0 deletions pkg/errors/factory_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package errors

import (
"github.com/pkg/errors"
"gotest.tools/assert"
api_errors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime/schema"
"testing"
)

func TestBuild(t *testing.T) {
//default non api error
Copy link
Contributor

@maximilien maximilien Aug 7, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would separate these cases into their own

t.Run(“default non API error”, func(t *testing.T){
})

This allows some parallelism, clarifies the output (and error) since each test gets the title added to it’s run output. You might need to be clever for setup and tear down. See some of the other tests for examples, e.g., tests in plugin or in core package.

defaultError := errors.New("my-custom-error")
err := Build(defaultError)
assert.Error(t, err, "my-custom-error")

gv := schema.GroupResource{
Group: "serving.knative.dev",
Resource: "service",
}

//api error containing expected error when knative crd is not available
apiError := api_errors.NewNotFound(gv, "serv")
apiError.Status().Details.Causes = []v1.StatusCause{
{
Type: "UnexpectedServerResponse",
Message: "404 page not found",
},
}
err = Build(apiError)
assert.Error(t, err, "no Knative serving API found on the backend. Please verify the installation.")

//api error not registered in error factory
apiError = api_errors.NewAlreadyExists(gv, "serv")
err = Build(apiError)
assert.Error(t, err, "service.serving.knative.dev \"serv\" already exists")

//default not found api error
apiError = api_errors.NewNotFound(gv, "serv")
err = Build(apiError)
assert.Error(t, err, "service.serving.knative.dev \"serv\" not found")
}
11 changes: 11 additions & 0 deletions pkg/errors/knerror.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package errors
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is a new package needed here? If this ends up being the only error, I would say no, if not then definitely. Not clear we had any new errors until now. Might be something to poll or get feedback from the group on.


func NewKNError(msg string) *KNError {
return &KNError{
msg: msg,
}
}

func (kne *KNError) Error() string {
return kne.msg
}
22 changes: 22 additions & 0 deletions pkg/errors/knerror_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package errors

import (
"gotest.tools/assert"
"testing"
)

func TestNewKNError(t *testing.T) {
err := NewKNError("myerror")
assert.Error(t, err, "myerror")

err = NewKNError("")
assert.Error(t, err, "")
}

func TestKNError_Error(t *testing.T) {
err := NewKNError("myerror")
assert.Equal(t, err.Error(), "myerror")

err = NewKNError("")
assert.Equal(t, err.Error(), "")
}
8 changes: 8 additions & 0 deletions pkg/errors/types.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package errors

import api_errors "k8s.io/apimachinery/pkg/api/errors"

type KNError struct {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

using a generic error struct for now, each error could use its own struct if neededin the future

Status api_errors.APIStatus
msg string
}
28 changes: 20 additions & 8 deletions pkg/serving/v1alpha1/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,11 @@ import (
"github.com/knative/client/pkg/serving"
"github.com/knative/client/pkg/wait"

kn_errors "github.com/knative/client/pkg/errors"
api_serving "github.com/knative/serving/pkg/apis/serving"
"github.com/knative/serving/pkg/apis/serving/v1alpha1"
client_v1alpha1 "github.com/knative/serving/pkg/client/clientset/versioned/typed/serving/v1alpha1"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime"
)
Expand Down Expand Up @@ -130,7 +131,7 @@ func NewKnServingClient(client client_v1alpha1.ServingV1alpha1Interface, namespa
func (cl *knClient) GetService(name string) (*v1alpha1.Service, error) {
service, err := cl.client.Services(cl.namespace).Get(name, v1.GetOptions{})
if err != nil {
return nil, err
return nil, kn_errors.Build(err)
}
err = serving.UpdateGroupVersionKind(service, v1alpha1.SchemeGroupVersion)
if err != nil {
Expand All @@ -143,7 +144,7 @@ func (cl *knClient) GetService(name string) (*v1alpha1.Service, error) {
func (cl *knClient) ListServices(config ...ListConfig) (*v1alpha1.ServiceList, error) {
serviceList, err := cl.client.Services(cl.namespace).List(ListConfigs(config).toListOptions())
if err != nil {
return nil, err
return nil, kn_errors.Build(err)
}
serviceListNew := serviceList.DeepCopy()
err = updateServingGvk(serviceListNew)
Expand All @@ -167,7 +168,7 @@ func (cl *knClient) ListServices(config ...ListConfig) (*v1alpha1.ServiceList, e
func (cl *knClient) CreateService(service *v1alpha1.Service) error {
_, err := cl.client.Services(cl.namespace).Create(service)
if err != nil {
return err
return kn_errors.Build(err)
}
return updateServingGvk(service)
}
Expand All @@ -183,10 +184,15 @@ func (cl *knClient) UpdateService(service *v1alpha1.Service) error {

// Delete a service by name
func (cl *knClient) DeleteService(serviceName string) error {
return cl.client.Services(cl.namespace).Delete(
err := cl.client.Services(cl.namespace).Delete(
serviceName,
&v1.DeleteOptions{},
)
if err != nil {
return kn_errors.Build(err)
}

return nil
}

// Wait for a service to become ready, but not longer than provided timeout
Expand All @@ -199,7 +205,7 @@ func (cl *knClient) WaitForService(name string, timeout time.Duration) error {
func (cl *knClient) GetRevision(name string) (*v1alpha1.Revision, error) {
revision, err := cl.client.Revisions(cl.namespace).Get(name, v1.GetOptions{})
if err != nil {
return nil, err
return nil, kn_errors.Build(err)
}
err = updateServingGvk(revision)
if err != nil {
Expand All @@ -210,14 +216,19 @@ func (cl *knClient) GetRevision(name string) (*v1alpha1.Revision, error) {

// Delete a revision by name
func (cl *knClient) DeleteRevision(name string) error {
return cl.client.Revisions(cl.namespace).Delete(name, &v1.DeleteOptions{})
err := cl.client.Revisions(cl.namespace).Delete(name, &v1.DeleteOptions{})
if err != nil {
return kn_errors.Build(err)
}

return nil
}

// List revisions
func (cl *knClient) ListRevisions(config ...ListConfig) (*v1alpha1.RevisionList, error) {
revisionList, err := cl.client.Revisions(cl.namespace).List(ListConfigs(config).toListOptions())
if err != nil {
return nil, err
return nil, kn_errors.Build(err)
}
return updateServingGvkForRevisionList(revisionList)
}
Expand Down Expand Up @@ -308,3 +319,4 @@ func serviceConditionExtractor(obj runtime.Object) (apis.Conditions, error) {
}
return apis.Conditions(service.Status.Conditions), nil
}