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

Add storage.Warnings to client #562

Merged
merged 1 commit into from
May 21, 2019
Merged

Conversation

jacksontj
Copy link
Contributor

@jacksontj jacksontj commented Apr 29, 2019

closes #560

This adds storage.Warnings to the error return of Query and QueryRange. Unfortunately the client.Do() method interface makes this a bit difficult to implement.

So either we go down this path (where the specific error is a known type people should check for) or we need to change the Do() methods' interface to return a separate "warning" error.

@jacksontj
Copy link
Contributor Author

Cc @krasi-georgiev

@jacksontj jacksontj force-pushed the issue_560 branch 3 times, most recently from e45a706 to a412d02 Compare April 30, 2019 17:03
Copy link
Contributor

@krasi-georgiev krasi-georgiev left a comment

Choose a reason for hiding this comment

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

I quick note now, but will do a full review soon.

api/prometheus/v1/api.go Outdated Show resolved Hide resolved
@jacksontj
Copy link
Contributor Author

@krasi-georgiev the main question is what the interface should be. Right now I'm wrapping the Warnings into a specific error type, which is different than the prom internals, but changes less of this client. Alternatively I can make the return of Do() add an additional return type of Warnings and have it be nil for everyone else -- either way works for me.

@jacksontj
Copy link
Contributor Author

@krasi-georgiev bump?

@krasi-georgiev
Copy link
Contributor

will review in 1-2 days.

@krasi-georgiev
Copy link
Contributor

I think it should be something like:

type Error struct {
	Type   ErrorType
	Msg    string
	Detail string
	Warnings []string
}

@jacksontj
Copy link
Contributor Author

@krasi-georgiev to clarify, you want to replace the error in the return with *Error? Or are you suggesting that Warnings be this new Error struct?.

From the upstream behavior warnings != error, so it seems incorrect for the client to be merging them. And if we decided that we wanted to then we'd definitely need a mechanism for the caller to determine what is an error vs a warning. In this proposed error struct thats a bit unclear. I'm also unclear what detail in the error would be, as the response struct (as taken from prometheus' api server) is:

type response struct {
	Status    status      `json:"status"`
	Data      interface{} `json:"data,omitempty"`
	ErrorType errorType   `json:"errorType,omitempty"`
	Error     string      `json:"error,omitempty"`
	Warnings  []string    `json:"warnings,omitempty"`
}

@krasi-georgiev
Copy link
Contributor

maybe you are right lets try to keep these separately to be similiar to upstream.

in that case why not keep it seperately starting from the client?

func (c apiClient) Do(ctx context.Context, req *http.Request) (*http.Response, []byte,Warnings,error) {

@jacksontj
Copy link
Contributor Author

@krasi-georgiev It isn't split right now because I hadn't heard back on the plan -- #562 (comment)

So it sounds like we want to keep warning/error separate all the way down? If so I can definitely do that.

@jacksontj
Copy link
Contributor Author

#562 (comment)

All the way down to the HTTP client doesn't quite work, at this client level all we do is return a resp, body, and error -- at this level we haven't unmarshaled the response enough to know if there are warnings.

@jacksontj jacksontj force-pushed the issue_560 branch 2 times, most recently from 4175e10 to 13dc6f7 Compare May 8, 2019 14:52
@jacksontj
Copy link
Contributor Author

@krasi-georgiev here's it with warnings split all the way down

Copy link
Contributor

@krasi-georgiev krasi-georgiev left a comment

Choose a reason for hiding this comment

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

hm , now I see what you meant by typecasting the error type.

Now when it is completely separated it pollutes all other methods that don't really need it and end up with _, _, _, err = h.client.Do(ctx, req)

Do you think that maybe we can implement some custom API error interface like
something like

type ErrorAPI interface{
	Error()
	Warning()
}

api/prometheus/v1/api_test.go Outdated Show resolved Hide resolved
api/prometheus/v1/api_test.go Outdated Show resolved Hide resolved
@jacksontj
Copy link
Contributor Author

For the error response, if we want to have a single return type, IMO we should go back to what I had before where we have a specific Warnings struct that implements error. This way we the interface remains the same and the client can decide if they want to treat warnings differently or not. IMO this is how upstream prom should have dealt with it -- but do realize that will make the client interface different from the internal interface.

@krasi-georgiev
Copy link
Contributor

krasi-georgiev commented May 10, 2019

For the error response, if we want to have a single return type, IMO we should go back to what I had before where we have a specific Warnings struct that implements error.

do you mean with typecasting? Do you think we can avoid typecasting by using an interface?

but do realize that will make the client interface different from the internal interface.

I don't think this is a problem.

@jacksontj
Copy link
Contributor Author

jacksontj commented May 10, 2019 via email

@krasi-georgiev
Copy link
Contributor

I was thinking something like this:

type ErrorAPI struct {
	error    string
	warning  string
}

func (w *ErrorAPI) Error() string {
	return w.error
}

func (w *ErrorAPI) Warning() string {
	return w.warnings
}

type Error interface {
	error
	Warning() string
}

and than

func (c *httpClient) Do(ctx context.Context, req *http.Request) (*http.Response, []byte, Error) {

so that the caller can use err.Error() and err.Warning() as needed.

@jacksontj
Copy link
Contributor Author

@krasi-georgiev Updated with a single interface for the error, some small cleanup required; but since we are going back and forth a bunch I figured I'll let you do an initial review before I spend the time cleaning it up all the way.

@krasi-georgiev
Copy link
Contributor

yes, I think this is the best approach so far so go ahead with the cleanup and ping me when ready for a review.

@jacksontj
Copy link
Contributor Author

@krasi-georgiev Cleanup done, hopefully thats enough to get this in :)

api/client.go Outdated Show resolved Hide resolved

type Error interface {
error
Err() error
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 need for Err() and Error()?

Copy link
Contributor Author

@jacksontj jacksontj May 15, 2019

Choose a reason for hiding this comment

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

Err() returns the error itself. This is important if you want to check if the error is something in particular. This is especially important as we have been returning the err directly until now. So old code that did something like:

ret, err := client.DoSomething()
if err != nil {
    return nil, err
}

would now be able to do something like

ret, err := client.DoSomething()
if err != nil && err.Err() != nil {
    return nil, err.Err()
}

Copy link
Contributor

@krasi-georgiev krasi-georgiev May 15, 2019

Choose a reason for hiding this comment

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

hm I still don't see the need for this.
Can you give an example for how and where it is needed and why it can't be achieved by just using err.Error() ?

even without err.Err you can still have

if err != nil && err.Error() != "" {
    return nil, err.Error()
}

Copy link
Contributor Author

Choose a reason for hiding this comment

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

err.Error() returns a string, if you need to check that is a specific error type or something then it is not sufficient.

For example, when we do a client get we could get back an error from the HTTP library. In the client it looks like so:

	_, body, apiErr := h.client.Do(ctx, req)
	if apiErr != nil {
		return AlertsResult{}, apiErr
	}

So if I wanted to handle the specific error type of http.ProtocolError I'd need to do something like:

if err != nil {
    if typedErr, ok := err.Err().(*http.ProtocolError); ok {
        // do something else
    }
    return nil, err
}

If we only have the string representation the people are forced to resort to string matching, which is a bad plan :)

Copy link
Contributor

Choose a reason for hiding this comment

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

Yes I agree. Lets keep just

// Error .....
type Error interface {
	// Error ......
	Error() error
	// Warnings returns a list of warnings
	Warnings() []string
}

There are still few places without comments the go-lint complains about it.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

As I'm thinking about this more, I think we want to stick with:

type Error interface{
    error
    Err() error
    Warnings() []string
}

and change the implementations of Error() (the string return) to check the warnings. This way its more-or-less backwards compatible -- although it would mean that warnings are errors (if you wanted to skip them you'd have to check the Err() method). This would make it more compatible, and a little less clunky.

If we want the default behavior to be that people have to check if it was an error or warning then I think going to some interface that doesn't adhere to the error interface would be okay -- it will just be completely incompatible with all client usage.

Copy link
Contributor

Choose a reason for hiding this comment

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

Yes you are right lets keep as is for now and will refactor if needed.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@krasi-georgiev to clarify, do you want me to change the Error() implementation to return something if there was a warning (e.g. if no error but warnings, return the warnings concated somehow)?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@krasi-georgiev bump :)

Copy link
Contributor

Choose a reason for hiding this comment

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

Sorry, I am at kubecon so will be slow on replies.
I meant to keep it as

type Error interface{
    error
    Err() error
    Warnings() []string
}

api/client.go Show resolved Hide resolved
api/prometheus/v1/api_test.go Outdated Show resolved Hide resolved
@jacksontj
Copy link
Contributor Author

@krasi-georgiev PR updated to address comments

Copy link
Contributor

@krasi-georgiev krasi-georgiev left a comment

Choose a reason for hiding this comment

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

I think these are the last nits I could find 😺

You are right about the interface lets keep as is for now and will refactor if needed.

api/client.go Show resolved Hide resolved
api/client.go Outdated Show resolved Hide resolved

type Error interface {
error
Err() error
Copy link
Contributor

Choose a reason for hiding this comment

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

Yes you are right lets keep as is for now and will refactor if needed.

api/prometheus/v1/api_test.go Outdated Show resolved Hide resolved
api/prometheus/v1/api_test.go Outdated Show resolved Hide resolved
api/prometheus/v1/api_test.go Outdated Show resolved Hide resolved
api/prometheus/v1/api_test.go Outdated Show resolved Hide resolved
@krasi-georgiev
Copy link
Contributor

LGTM
@beorn7 any comment from your side?

Signed-off-by: Thomas Jackson <jacksontj.89@gmail.com>

Fixes prometheus#560
@jacksontj
Copy link
Contributor Author

@krasi-georgiev squashed, should be ready for merge.

@beorn7
Copy link
Member

beorn7 commented May 21, 2019

Didn't check all the details, but no general remarks from my side. I trust you to do the right thing in detail.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Add "storage.Warnings" to api client
4 participants