Pass warnings through on non-error responses (#599)

Return warnings as a separate string slice to simplify handling.

Signed-off-by: Thomas Jackson <jacksontj.89@gmail.com>
This commit is contained in:
Thomas Jackson 2019-06-13 16:40:59 -07:00 committed by Krasi Georgiev
parent e7f6132a76
commit 1335ef46bd
4 changed files with 241 additions and 242 deletions

View File

@ -25,41 +25,7 @@ import (
"time" "time"
) )
func NewErrorAPI(err error, warnings []string) Error { type Warnings []string
if err == nil && warnings == nil {
return nil
}
return &ErrorAPI{err, warnings}
}
type ErrorAPI struct {
err error
warnings []string
}
func (w *ErrorAPI) Err() error {
return w.err
}
func (w *ErrorAPI) Error() string {
if w.err != nil {
return w.err.Error()
}
return "Warnings: " + strings.Join(w.warnings, " , ")
}
func (w *ErrorAPI) Warnings() []string {
return w.warnings
}
// Error encapsulates an error + warning
type Error interface {
error
// Err returns the underlying error.
Err() error
// Warnings returns a list of warnings.
Warnings() []string
}
// DefaultRoundTripper is used if no RoundTripper is set in Config. // DefaultRoundTripper is used if no RoundTripper is set in Config.
var DefaultRoundTripper http.RoundTripper = &http.Transport{ var DefaultRoundTripper http.RoundTripper = &http.Transport{
@ -91,30 +57,30 @@ func (cfg *Config) roundTripper() http.RoundTripper {
// Client is the interface for an API client. // Client is the interface for an API client.
type Client interface { type Client interface {
URL(ep string, args map[string]string) *url.URL URL(ep string, args map[string]string) *url.URL
Do(context.Context, *http.Request) (*http.Response, []byte, Error) Do(context.Context, *http.Request) (*http.Response, []byte, Warnings, error)
} }
// DoGetFallback will attempt to do the request as-is, and on a 405 it will fallback to a GET request. // DoGetFallback will attempt to do the request as-is, and on a 405 it will fallback to a GET request.
func DoGetFallback(c Client, ctx context.Context, u *url.URL, args url.Values) (*http.Response, []byte, Error) { func DoGetFallback(c Client, ctx context.Context, u *url.URL, args url.Values) (*http.Response, []byte, Warnings, error) {
req, err := http.NewRequest(http.MethodPost, u.String(), strings.NewReader(args.Encode())) req, err := http.NewRequest(http.MethodPost, u.String(), strings.NewReader(args.Encode()))
if err != nil { if err != nil {
return nil, nil, NewErrorAPI(err, nil) return nil, nil, nil, err
} }
req.Header.Set("Content-Type", "application/x-www-form-urlencoded") req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
resp, body, err := c.Do(ctx, req) resp, body, warnings, err := c.Do(ctx, req)
if resp != nil && resp.StatusCode == http.StatusMethodNotAllowed { if resp != nil && resp.StatusCode == http.StatusMethodNotAllowed {
u.RawQuery = args.Encode() u.RawQuery = args.Encode()
req, err = http.NewRequest(http.MethodGet, u.String(), nil) req, err = http.NewRequest(http.MethodGet, u.String(), nil)
if err != nil { if err != nil {
return nil, nil, NewErrorAPI(err, nil) return nil, nil, warnings, err
} }
} else { } else {
if err != nil { if err != nil {
return resp, body, NewErrorAPI(err, nil) return resp, body, warnings, err
} }
return resp, body, nil return resp, body, warnings, nil
} }
return c.Do(ctx, req) return c.Do(ctx, req)
} }
@ -154,7 +120,7 @@ func (c *httpClient) URL(ep string, args map[string]string) *url.URL {
return &u return &u
} }
func (c *httpClient) Do(ctx context.Context, req *http.Request) (*http.Response, []byte, Error) { func (c *httpClient) Do(ctx context.Context, req *http.Request) (*http.Response, []byte, Warnings, error) {
if ctx != nil { if ctx != nil {
req = req.WithContext(ctx) req = req.WithContext(ctx)
} }
@ -166,7 +132,7 @@ func (c *httpClient) Do(ctx context.Context, req *http.Request) (*http.Response,
}() }()
if err != nil { if err != nil {
return nil, nil, NewErrorAPI(err, nil) return nil, nil, nil, err
} }
var body []byte var body []byte
@ -186,5 +152,5 @@ func (c *httpClient) Do(ctx context.Context, req *http.Request) (*http.Response,
case <-done: case <-done:
} }
return resp, body, NewErrorAPI(err, nil) return resp, body, nil, err
} }

View File

@ -152,7 +152,7 @@ func TestDoGetFallback(t *testing.T) {
client := &httpClient{client: *(server.Client())} client := &httpClient{client: *(server.Client())}
// Do a post, and ensure that the post succeeds. // Do a post, and ensure that the post succeeds.
_, b, err := DoGetFallback(client, context.TODO(), u, v) _, b, _, err := DoGetFallback(client, context.TODO(), u, v)
if err != nil { if err != nil {
t.Fatalf("Error doing local request: %v", err) t.Fatalf("Error doing local request: %v", err)
} }
@ -169,7 +169,7 @@ func TestDoGetFallback(t *testing.T) {
// Do a fallbcak to a get. // Do a fallbcak to a get.
u.Path = "/blockPost" u.Path = "/blockPost"
_, b, err = DoGetFallback(client, context.TODO(), u, v) _, b, _, err = DoGetFallback(client, context.TODO(), u, v)
if err != nil { if err != nil {
t.Fatalf("Error doing local request: %v", err) t.Fatalf("Error doing local request: %v", err)
} }

View File

@ -22,7 +22,6 @@ import (
"math" "math"
"net/http" "net/http"
"strconv" "strconv"
"strings"
"time" "time"
"unsafe" "unsafe"
@ -197,26 +196,13 @@ const (
// Error is an error returned by the API. // Error is an error returned by the API.
type Error struct { type Error struct {
Type ErrorType Type ErrorType
Msg string Msg string
Detail string Detail string
warnings []string
} }
func (e *Error) Error() string { func (e *Error) Error() string {
if e.Type != "" || e.Msg != "" { return fmt.Sprintf("%s: %s", e.Type, e.Msg)
return fmt.Sprintf("%s: %s", e.Type, e.Msg)
}
return "Warnings: " + strings.Join(e.warnings, " , ")
}
func (w *Error) Err() error {
return w
}
func (w *Error) Warnings() []string {
return w.warnings
} }
// Range represents a sliced time range. // Range represents a sliced time range.
@ -230,34 +216,34 @@ type Range struct {
// API provides bindings for Prometheus's v1 API. // API provides bindings for Prometheus's v1 API.
type API interface { type API interface {
// Alerts returns a list of all active alerts. // Alerts returns a list of all active alerts.
Alerts(ctx context.Context) (AlertsResult, api.Error) Alerts(ctx context.Context) (AlertsResult, error)
// AlertManagers returns an overview of the current state of the Prometheus alert manager discovery. // AlertManagers returns an overview of the current state of the Prometheus alert manager discovery.
AlertManagers(ctx context.Context) (AlertManagersResult, api.Error) AlertManagers(ctx context.Context) (AlertManagersResult, error)
// CleanTombstones removes the deleted data from disk and cleans up the existing tombstones. // CleanTombstones removes the deleted data from disk and cleans up the existing tombstones.
CleanTombstones(ctx context.Context) api.Error CleanTombstones(ctx context.Context) error
// Config returns the current Prometheus configuration. // Config returns the current Prometheus configuration.
Config(ctx context.Context) (ConfigResult, api.Error) Config(ctx context.Context) (ConfigResult, error)
// DeleteSeries deletes data for a selection of series in a time range. // DeleteSeries deletes data for a selection of series in a time range.
DeleteSeries(ctx context.Context, matches []string, startTime time.Time, endTime time.Time) api.Error DeleteSeries(ctx context.Context, matches []string, startTime time.Time, endTime time.Time) error
// Flags returns the flag values that Prometheus was launched with. // Flags returns the flag values that Prometheus was launched with.
Flags(ctx context.Context) (FlagsResult, api.Error) Flags(ctx context.Context) (FlagsResult, error)
// LabelValues performs a query for the values of the given label. // LabelValues performs a query for the values of the given label.
LabelValues(ctx context.Context, label string) (model.LabelValues, api.Error) LabelValues(ctx context.Context, label string) (model.LabelValues, error)
// Query performs a query for the given time. // Query performs a query for the given time.
Query(ctx context.Context, query string, ts time.Time) (model.Value, api.Error) Query(ctx context.Context, query string, ts time.Time) (model.Value, api.Warnings, error)
// QueryRange performs a query for the given range. // QueryRange performs a query for the given range.
QueryRange(ctx context.Context, query string, r Range) (model.Value, api.Error) QueryRange(ctx context.Context, query string, r Range) (model.Value, api.Warnings, error)
// Series finds series by label matchers. // Series finds series by label matchers.
Series(ctx context.Context, matches []string, startTime time.Time, endTime time.Time) ([]model.LabelSet, api.Error) Series(ctx context.Context, matches []string, startTime time.Time, endTime time.Time) ([]model.LabelSet, error)
// Snapshot creates a snapshot of all current data into snapshots/<datetime>-<rand> // Snapshot creates a snapshot of all current data into snapshots/<datetime>-<rand>
// under the TSDB's data directory and returns the directory as response. // under the TSDB's data directory and returns the directory as response.
Snapshot(ctx context.Context, skipHead bool) (SnapshotResult, api.Error) Snapshot(ctx context.Context, skipHead bool) (SnapshotResult, error)
// Rules returns a list of alerting and recording rules that are currently loaded. // Rules returns a list of alerting and recording rules that are currently loaded.
Rules(ctx context.Context) (RulesResult, api.Error) Rules(ctx context.Context) (RulesResult, error)
// Targets returns an overview of the current state of the Prometheus target discovery. // Targets returns an overview of the current state of the Prometheus target discovery.
Targets(ctx context.Context) (TargetsResult, api.Error) Targets(ctx context.Context) (TargetsResult, error)
// TargetsMetadata returns metadata about metrics currently scraped by the target. // TargetsMetadata returns metadata about metrics currently scraped by the target.
TargetsMetadata(ctx context.Context, matchTarget string, metric string, limit string) ([]MetricMetadata, api.Error) TargetsMetadata(ctx context.Context, matchTarget string, metric string, limit string) ([]MetricMetadata, error)
} }
// AlertsResult contains the result from querying the alerts endpoint. // AlertsResult contains the result from querying the alerts endpoint.
@ -534,73 +520,70 @@ type httpAPI struct {
client api.Client client api.Client
} }
func (h *httpAPI) Alerts(ctx context.Context) (AlertsResult, api.Error) { func (h *httpAPI) Alerts(ctx context.Context) (AlertsResult, error) {
u := h.client.URL(epAlerts, nil) u := h.client.URL(epAlerts, nil)
req, err := http.NewRequest(http.MethodGet, u.String(), nil) req, err := http.NewRequest(http.MethodGet, u.String(), nil)
if err != nil { if err != nil {
return AlertsResult{}, api.NewErrorAPI(err, nil) return AlertsResult{}, err
} }
_, body, apiErr := h.client.Do(ctx, req) _, body, _, err := h.client.Do(ctx, req)
if apiErr != nil { if err != nil {
return AlertsResult{}, apiErr return AlertsResult{}, err
} }
var res AlertsResult var res AlertsResult
err = json.Unmarshal(body, &res) return res, json.Unmarshal(body, &res)
return res, api.NewErrorAPI(err, nil)
} }
func (h *httpAPI) AlertManagers(ctx context.Context) (AlertManagersResult, api.Error) { func (h *httpAPI) AlertManagers(ctx context.Context) (AlertManagersResult, error) {
u := h.client.URL(epAlertManagers, nil) u := h.client.URL(epAlertManagers, nil)
req, err := http.NewRequest(http.MethodGet, u.String(), nil) req, err := http.NewRequest(http.MethodGet, u.String(), nil)
if err != nil { if err != nil {
return AlertManagersResult{}, api.NewErrorAPI(err, nil) return AlertManagersResult{}, err
} }
_, body, apiErr := h.client.Do(ctx, req) _, body, _, err := h.client.Do(ctx, req)
if apiErr != nil { if err != nil {
return AlertManagersResult{}, apiErr return AlertManagersResult{}, err
} }
var res AlertManagersResult var res AlertManagersResult
err = json.Unmarshal(body, &res) return res, json.Unmarshal(body, &res)
return res, api.NewErrorAPI(err, nil)
} }
func (h *httpAPI) CleanTombstones(ctx context.Context) api.Error { func (h *httpAPI) CleanTombstones(ctx context.Context) error {
u := h.client.URL(epCleanTombstones, nil) u := h.client.URL(epCleanTombstones, nil)
req, err := http.NewRequest(http.MethodPost, u.String(), nil) req, err := http.NewRequest(http.MethodPost, u.String(), nil)
if err != nil { if err != nil {
return api.NewErrorAPI(err, nil) return err
} }
_, _, apiErr := h.client.Do(ctx, req) _, _, _, err = h.client.Do(ctx, req)
return apiErr return err
} }
func (h *httpAPI) Config(ctx context.Context) (ConfigResult, api.Error) { func (h *httpAPI) Config(ctx context.Context) (ConfigResult, error) {
u := h.client.URL(epConfig, nil) u := h.client.URL(epConfig, nil)
req, err := http.NewRequest(http.MethodGet, u.String(), nil) req, err := http.NewRequest(http.MethodGet, u.String(), nil)
if err != nil { if err != nil {
return ConfigResult{}, api.NewErrorAPI(err, nil) return ConfigResult{}, err
} }
_, body, apiErr := h.client.Do(ctx, req) _, body, _, err := h.client.Do(ctx, req)
if apiErr != nil { if err != nil {
return ConfigResult{}, apiErr return ConfigResult{}, err
} }
var res ConfigResult var res ConfigResult
err = json.Unmarshal(body, &res) return res, json.Unmarshal(body, &res)
return res, api.NewErrorAPI(err, nil)
} }
func (h *httpAPI) DeleteSeries(ctx context.Context, matches []string, startTime time.Time, endTime time.Time) api.Error { func (h *httpAPI) DeleteSeries(ctx context.Context, matches []string, startTime time.Time, endTime time.Time) error {
u := h.client.URL(epDeleteSeries, nil) u := h.client.URL(epDeleteSeries, nil)
q := u.Query() q := u.Query()
@ -615,47 +598,45 @@ func (h *httpAPI) DeleteSeries(ctx context.Context, matches []string, startTime
req, err := http.NewRequest(http.MethodPost, u.String(), nil) req, err := http.NewRequest(http.MethodPost, u.String(), nil)
if err != nil { if err != nil {
return api.NewErrorAPI(err, nil) return err
} }
_, _, apiErr := h.client.Do(ctx, req) _, _, _, err = h.client.Do(ctx, req)
return apiErr return err
} }
func (h *httpAPI) Flags(ctx context.Context) (FlagsResult, api.Error) { func (h *httpAPI) Flags(ctx context.Context) (FlagsResult, error) {
u := h.client.URL(epFlags, nil) u := h.client.URL(epFlags, nil)
req, err := http.NewRequest(http.MethodGet, u.String(), nil) req, err := http.NewRequest(http.MethodGet, u.String(), nil)
if err != nil { if err != nil {
return FlagsResult{}, api.NewErrorAPI(err, nil) return FlagsResult{}, err
} }
_, body, apiErr := h.client.Do(ctx, req) _, body, _, err := h.client.Do(ctx, req)
if apiErr != nil { if err != nil {
return FlagsResult{}, apiErr return FlagsResult{}, err
} }
var res FlagsResult var res FlagsResult
err = json.Unmarshal(body, &res) return res, json.Unmarshal(body, &res)
return res, api.NewErrorAPI(err, nil)
} }
func (h *httpAPI) LabelValues(ctx context.Context, label string) (model.LabelValues, api.Error) { func (h *httpAPI) LabelValues(ctx context.Context, label string) (model.LabelValues, error) {
u := h.client.URL(epLabelValues, map[string]string{"name": label}) u := h.client.URL(epLabelValues, map[string]string{"name": label})
req, err := http.NewRequest(http.MethodGet, u.String(), nil) req, err := http.NewRequest(http.MethodGet, u.String(), nil)
if err != nil { if err != nil {
return nil, api.NewErrorAPI(err, nil) return nil, err
} }
_, body, apiErr := h.client.Do(ctx, req) _, body, _, err := h.client.Do(ctx, req)
if apiErr != nil { if err != nil {
return nil, apiErr return nil, err
} }
var labelValues model.LabelValues var labelValues model.LabelValues
err = json.Unmarshal(body, &labelValues) return labelValues, json.Unmarshal(body, &labelValues)
return labelValues, api.NewErrorAPI(err, nil)
} }
func (h *httpAPI) Query(ctx context.Context, query string, ts time.Time) (model.Value, api.Error) { func (h *httpAPI) Query(ctx context.Context, query string, ts time.Time) (model.Value, api.Warnings, error) {
u := h.client.URL(epQuery, nil) u := h.client.URL(epQuery, nil)
q := u.Query() q := u.Query()
@ -664,16 +645,16 @@ func (h *httpAPI) Query(ctx context.Context, query string, ts time.Time) (model.
q.Set("time", ts.Format(time.RFC3339Nano)) q.Set("time", ts.Format(time.RFC3339Nano))
} }
_, body, apiErr := api.DoGetFallback(h.client, ctx, u, q) _, body, warnings, err := api.DoGetFallback(h.client, ctx, u, q)
if apiErr != nil { if err != nil {
return nil, apiErr return nil, warnings, err
} }
var qres queryResult var qres queryResult
return model.Value(qres.v), api.NewErrorAPI(json.Unmarshal(body, &qres), nil) return model.Value(qres.v), warnings, json.Unmarshal(body, &qres)
} }
func (h *httpAPI) QueryRange(ctx context.Context, query string, r Range) (model.Value, api.Error) { func (h *httpAPI) QueryRange(ctx context.Context, query string, r Range) (model.Value, api.Warnings, error) {
u := h.client.URL(epQueryRange, nil) u := h.client.URL(epQueryRange, nil)
q := u.Query() q := u.Query()
@ -688,17 +669,17 @@ func (h *httpAPI) QueryRange(ctx context.Context, query string, r Range) (model.
q.Set("end", end) q.Set("end", end)
q.Set("step", step) q.Set("step", step)
_, body, apiErr := api.DoGetFallback(h.client, ctx, u, q) _, body, warnings, err := api.DoGetFallback(h.client, ctx, u, q)
if apiErr != nil { if err != nil {
return nil, apiErr return nil, warnings, err
} }
var qres queryResult var qres queryResult
return model.Value(qres.v), api.NewErrorAPI(json.Unmarshal(body, &qres), nil) return model.Value(qres.v), warnings, json.Unmarshal(body, &qres)
} }
func (h *httpAPI) Series(ctx context.Context, matches []string, startTime time.Time, endTime time.Time) ([]model.LabelSet, api.Error) { func (h *httpAPI) Series(ctx context.Context, matches []string, startTime time.Time, endTime time.Time) ([]model.LabelSet, error) {
u := h.client.URL(epSeries, nil) u := h.client.URL(epSeries, nil)
q := u.Query() q := u.Query()
@ -713,20 +694,19 @@ func (h *httpAPI) Series(ctx context.Context, matches []string, startTime time.T
req, err := http.NewRequest(http.MethodGet, u.String(), nil) req, err := http.NewRequest(http.MethodGet, u.String(), nil)
if err != nil { if err != nil {
return nil, api.NewErrorAPI(err, nil) return nil, err
} }
_, body, apiErr := h.client.Do(ctx, req) _, body, _, err := h.client.Do(ctx, req)
if apiErr != nil { if err != nil {
return nil, apiErr return nil, err
} }
var mset []model.LabelSet var mset []model.LabelSet
err = json.Unmarshal(body, &mset) return mset, json.Unmarshal(body, &mset)
return mset, api.NewErrorAPI(err, nil)
} }
func (h *httpAPI) Snapshot(ctx context.Context, skipHead bool) (SnapshotResult, api.Error) { func (h *httpAPI) Snapshot(ctx context.Context, skipHead bool) (SnapshotResult, error) {
u := h.client.URL(epSnapshot, nil) u := h.client.URL(epSnapshot, nil)
q := u.Query() q := u.Query()
@ -736,56 +716,53 @@ func (h *httpAPI) Snapshot(ctx context.Context, skipHead bool) (SnapshotResult,
req, err := http.NewRequest(http.MethodPost, u.String(), nil) req, err := http.NewRequest(http.MethodPost, u.String(), nil)
if err != nil { if err != nil {
return SnapshotResult{}, api.NewErrorAPI(err, nil) return SnapshotResult{}, err
} }
_, body, apiErr := h.client.Do(ctx, req) _, body, _, err := h.client.Do(ctx, req)
if apiErr != nil { if err != nil {
return SnapshotResult{}, apiErr return SnapshotResult{}, err
} }
var res SnapshotResult var res SnapshotResult
err = json.Unmarshal(body, &res) return res, json.Unmarshal(body, &res)
return res, api.NewErrorAPI(err, nil)
} }
func (h *httpAPI) Rules(ctx context.Context) (RulesResult, api.Error) { func (h *httpAPI) Rules(ctx context.Context) (RulesResult, error) {
u := h.client.URL(epRules, nil) u := h.client.URL(epRules, nil)
req, err := http.NewRequest(http.MethodGet, u.String(), nil) req, err := http.NewRequest(http.MethodGet, u.String(), nil)
if err != nil { if err != nil {
return RulesResult{}, api.NewErrorAPI(err, nil) return RulesResult{}, err
} }
_, body, apiErr := h.client.Do(ctx, req) _, body, _, err := h.client.Do(ctx, req)
if apiErr != nil { if err != nil {
return RulesResult{}, apiErr return RulesResult{}, err
} }
var res RulesResult var res RulesResult
err = json.Unmarshal(body, &res) return res, json.Unmarshal(body, &res)
return res, api.NewErrorAPI(err, nil)
} }
func (h *httpAPI) Targets(ctx context.Context) (TargetsResult, api.Error) { func (h *httpAPI) Targets(ctx context.Context) (TargetsResult, error) {
u := h.client.URL(epTargets, nil) u := h.client.URL(epTargets, nil)
req, err := http.NewRequest(http.MethodGet, u.String(), nil) req, err := http.NewRequest(http.MethodGet, u.String(), nil)
if err != nil { if err != nil {
return TargetsResult{}, api.NewErrorAPI(err, nil) return TargetsResult{}, err
} }
_, body, apiErr := h.client.Do(ctx, req) _, body, _, err := h.client.Do(ctx, req)
if apiErr != nil { if err != nil {
return TargetsResult{}, apiErr return TargetsResult{}, err
} }
var res TargetsResult var res TargetsResult
err = json.Unmarshal(body, &res) return res, json.Unmarshal(body, &res)
return res, api.NewErrorAPI(err, nil)
} }
func (h *httpAPI) TargetsMetadata(ctx context.Context, matchTarget string, metric string, limit string) ([]MetricMetadata, api.Error) { func (h *httpAPI) TargetsMetadata(ctx context.Context, matchTarget string, metric string, limit string) ([]MetricMetadata, error) {
u := h.client.URL(epTargetsMetadata, nil) u := h.client.URL(epTargetsMetadata, nil)
q := u.Query() q := u.Query()
@ -797,17 +774,16 @@ func (h *httpAPI) TargetsMetadata(ctx context.Context, matchTarget string, metri
req, err := http.NewRequest(http.MethodGet, u.String(), nil) req, err := http.NewRequest(http.MethodGet, u.String(), nil)
if err != nil { if err != nil {
return nil, api.NewErrorAPI(err, nil) return nil, err
} }
_, body, apiErr := h.client.Do(ctx, req) _, body, _, err := h.client.Do(ctx, req)
if apiErr != nil { if err != nil {
return nil, apiErr return nil, err
} }
var res []MetricMetadata var res []MetricMetadata
err = json.Unmarshal(body, &res) return res, json.Unmarshal(body, &res)
return res, api.NewErrorAPI(err, nil)
} }
// apiClient wraps a regular client and processes successful API responses. // apiClient wraps a regular client and processes successful API responses.
@ -839,19 +815,17 @@ func errorTypeAndMsgFor(resp *http.Response) (ErrorType, string) {
return ErrBadResponse, fmt.Sprintf("bad response code %d", resp.StatusCode) return ErrBadResponse, fmt.Sprintf("bad response code %d", resp.StatusCode)
} }
func (c apiClient) Do(ctx context.Context, req *http.Request) (*http.Response, []byte, api.Error) { func (c apiClient) Do(ctx context.Context, req *http.Request) (*http.Response, []byte, api.Warnings, error) {
resp, body, apiErr := c.Client.Do(ctx, req) resp, body, warnings, err := c.Client.Do(ctx, req)
if apiErr != nil { if err != nil {
return resp, body, apiErr return resp, body, warnings, err
} }
code := resp.StatusCode code := resp.StatusCode
var err api.Error
if code/100 != 2 && !apiError(code) { if code/100 != 2 && !apiError(code) {
errorType, errorMsg := errorTypeAndMsgFor(resp) errorType, errorMsg := errorTypeAndMsgFor(resp)
return resp, body, &Error{ return resp, body, warnings, &Error{
Type: errorType, Type: errorType,
Msg: errorMsg, Msg: errorMsg,
Detail: string(body), Detail: string(body),
@ -862,7 +836,7 @@ func (c apiClient) Do(ctx context.Context, req *http.Request) (*http.Response, [
if http.StatusNoContent != code { if http.StatusNoContent != code {
if jsonErr := json.Unmarshal(body, &result); jsonErr != nil { if jsonErr := json.Unmarshal(body, &result); jsonErr != nil {
return resp, body, &Error{ return resp, body, warnings, &Error{
Type: ErrBadResponse, Type: ErrBadResponse,
Msg: jsonErr.Error(), Msg: jsonErr.Error(),
} }
@ -871,20 +845,18 @@ func (c apiClient) Do(ctx context.Context, req *http.Request) (*http.Response, [
if apiError(code) != (result.Status == "error") { if apiError(code) != (result.Status == "error") {
err = &Error{ err = &Error{
Type: ErrBadResponse, Type: ErrBadResponse,
Msg: "inconsistent body for response code", Msg: "inconsistent body for response code",
warnings: result.Warnings,
} }
} }
if apiError(code) && result.Status == "error" { if apiError(code) && result.Status == "error" {
err = &Error{ err = &Error{
Type: result.ErrorType, Type: result.ErrorType,
Msg: result.Error, Msg: result.Error,
warnings: result.Warnings,
} }
} }
return resp, []byte(result.Data), err return resp, []byte(result.Data), warnings, err
} }

View File

@ -33,7 +33,7 @@ import (
) )
type apiTest struct { type apiTest struct {
do func() (interface{}, api.Error) do func() (interface{}, api.Warnings, error)
inWarnings []string inWarnings []string
inErr error inErr error
inStatusCode int inStatusCode int
@ -43,6 +43,7 @@ type apiTest struct {
reqParam url.Values reqParam url.Values
reqMethod string reqMethod string
res interface{} res interface{}
warnings api.Warnings
err error err error
} }
@ -63,7 +64,7 @@ func (c *apiTestClient) URL(ep string, args map[string]string) *url.URL {
return u return u
} }
func (c *apiTestClient) Do(ctx context.Context, req *http.Request) (*http.Response, []byte, api.Error) { func (c *apiTestClient) Do(ctx context.Context, req *http.Request) (*http.Response, []byte, api.Warnings, error) {
test := c.curTest test := c.curTest
@ -88,7 +89,7 @@ func (c *apiTestClient) Do(ctx context.Context, req *http.Request) (*http.Respon
resp.StatusCode = http.StatusOK resp.StatusCode = http.StatusOK
} }
return resp, b, api.NewErrorAPI(test.inErr, test.inWarnings) return resp, b, test.inWarnings, test.inErr
} }
func TestAPIs(t *testing.T) { func TestAPIs(t *testing.T) {
@ -101,81 +102,90 @@ func TestAPIs(t *testing.T) {
client: client, client: client,
} }
doAlertManagers := func() func() (interface{}, api.Error) { doAlertManagers := func() func() (interface{}, api.Warnings, error) {
return func() (interface{}, api.Error) { return func() (interface{}, api.Warnings, error) {
return promAPI.AlertManagers(context.Background()) v, err := promAPI.AlertManagers(context.Background())
return v, nil, err
} }
} }
doCleanTombstones := func() func() (interface{}, api.Error) { doCleanTombstones := func() func() (interface{}, api.Warnings, error) {
return func() (interface{}, api.Error) { return func() (interface{}, api.Warnings, error) {
return nil, promAPI.CleanTombstones(context.Background()) return nil, nil, promAPI.CleanTombstones(context.Background())
} }
} }
doConfig := func() func() (interface{}, api.Error) { doConfig := func() func() (interface{}, api.Warnings, error) {
return func() (interface{}, api.Error) { return func() (interface{}, api.Warnings, error) {
return promAPI.Config(context.Background()) v, err := promAPI.Config(context.Background())
return v, nil, err
} }
} }
doDeleteSeries := func(matcher string, startTime time.Time, endTime time.Time) func() (interface{}, api.Error) { doDeleteSeries := func(matcher string, startTime time.Time, endTime time.Time) func() (interface{}, api.Warnings, error) {
return func() (interface{}, api.Error) { return func() (interface{}, api.Warnings, error) {
return nil, promAPI.DeleteSeries(context.Background(), []string{matcher}, startTime, endTime) return nil, nil, promAPI.DeleteSeries(context.Background(), []string{matcher}, startTime, endTime)
} }
} }
doFlags := func() func() (interface{}, api.Error) { doFlags := func() func() (interface{}, api.Warnings, error) {
return func() (interface{}, api.Error) { return func() (interface{}, api.Warnings, error) {
return promAPI.Flags(context.Background()) v, err := promAPI.Flags(context.Background())
return v, nil, err
} }
} }
doLabelValues := func(label string) func() (interface{}, api.Error) { doLabelValues := func(label string) func() (interface{}, api.Warnings, error) {
return func() (interface{}, api.Error) { return func() (interface{}, api.Warnings, error) {
return promAPI.LabelValues(context.Background(), label) v, err := promAPI.LabelValues(context.Background(), label)
return v, nil, err
} }
} }
doQuery := func(q string, ts time.Time) func() (interface{}, api.Error) { doQuery := func(q string, ts time.Time) func() (interface{}, api.Warnings, error) {
return func() (interface{}, api.Error) { return func() (interface{}, api.Warnings, error) {
return promAPI.Query(context.Background(), q, ts) return promAPI.Query(context.Background(), q, ts)
} }
} }
doQueryRange := func(q string, rng Range) func() (interface{}, api.Error) { doQueryRange := func(q string, rng Range) func() (interface{}, api.Warnings, error) {
return func() (interface{}, api.Error) { return func() (interface{}, api.Warnings, error) {
return promAPI.QueryRange(context.Background(), q, rng) return promAPI.QueryRange(context.Background(), q, rng)
} }
} }
doSeries := func(matcher string, startTime time.Time, endTime time.Time) func() (interface{}, api.Error) { doSeries := func(matcher string, startTime time.Time, endTime time.Time) func() (interface{}, api.Warnings, error) {
return func() (interface{}, api.Error) { return func() (interface{}, api.Warnings, error) {
return promAPI.Series(context.Background(), []string{matcher}, startTime, endTime) v, err := promAPI.Series(context.Background(), []string{matcher}, startTime, endTime)
return v, nil, err
} }
} }
doSnapshot := func(skipHead bool) func() (interface{}, api.Error) { doSnapshot := func(skipHead bool) func() (interface{}, api.Warnings, error) {
return func() (interface{}, api.Error) { return func() (interface{}, api.Warnings, error) {
return promAPI.Snapshot(context.Background(), skipHead) v, err := promAPI.Snapshot(context.Background(), skipHead)
return v, nil, err
} }
} }
doRules := func() func() (interface{}, api.Error) { doRules := func() func() (interface{}, api.Warnings, error) {
return func() (interface{}, api.Error) { return func() (interface{}, api.Warnings, error) {
return promAPI.Rules(context.Background()) v, err := promAPI.Rules(context.Background())
return v, nil, err
} }
} }
doTargets := func() func() (interface{}, api.Error) { doTargets := func() func() (interface{}, api.Warnings, error) {
return func() (interface{}, api.Error) { return func() (interface{}, api.Warnings, error) {
return promAPI.Targets(context.Background()) v, err := promAPI.Targets(context.Background())
return v, nil, err
} }
} }
doTargetsMetadata := func(matchTarget string, metric string, limit string) func() (interface{}, api.Error) { doTargetsMetadata := func(matchTarget string, metric string, limit string) func() (interface{}, api.Warnings, error) {
return func() (interface{}, api.Error) { return func() (interface{}, api.Warnings, error) {
return promAPI.TargetsMetadata(context.Background(), matchTarget, metric, limit) v, err := promAPI.TargetsMetadata(context.Background(), matchTarget, metric, limit)
return v, nil, err
} }
} }
@ -249,6 +259,51 @@ func TestAPIs(t *testing.T) {
}, },
err: errors.New("client_error: client error: 404"), err: errors.New("client_error: client error: 404"),
}, },
// Warning only.
{
do: doQuery("2", testTime),
inWarnings: []string{"warning"},
inRes: &queryResult{
Type: model.ValScalar,
Result: &model.Scalar{
Value: 2,
Timestamp: model.TimeFromUnix(testTime.Unix()),
},
},
reqMethod: "POST",
reqPath: "/api/v1/query",
reqParam: url.Values{
"query": []string{"2"},
"time": []string{testTime.Format(time.RFC3339Nano)},
},
res: &model.Scalar{
Value: 2,
Timestamp: model.TimeFromUnix(testTime.Unix()),
},
warnings: []string{"warning"},
},
// Warning + error.
{
do: doQuery("2", testTime),
inWarnings: []string{"warning"},
inRes: "some body",
inStatusCode: 404,
inErr: &Error{
Type: ErrClient,
Msg: "client error: 404",
Detail: "some body",
},
reqMethod: "POST",
reqPath: "/api/v1/query",
reqParam: url.Values{
"query": []string{"2"},
"time": []string{testTime.Format(time.RFC3339Nano)},
},
err: errors.New("client_error: client error: 404"),
warnings: []string{"warning"},
},
{ {
do: doQueryRange("2", Range{ do: doQueryRange("2", Range{
@ -705,7 +760,11 @@ func TestAPIs(t *testing.T) {
t.Run(fmt.Sprintf("%d", i), func(t *testing.T) { t.Run(fmt.Sprintf("%d", i), func(t *testing.T) {
client.curTest = test client.curTest = test
res, err := test.do() res, warnings, err := test.do()
if (test.inWarnings == nil) != (warnings == nil) && !reflect.DeepEqual(test.inWarnings, warnings) {
t.Fatalf("mismatch in warnings expected=%v actual=%v", test.inWarnings, warnings)
}
if test.err != nil { if test.err != nil {
if err == nil { if err == nil {
@ -740,17 +799,18 @@ type testClient struct {
} }
type apiClientTest struct { type apiClientTest struct {
code int code int
response interface{} response interface{}
expectedBody string expectedBody string
expectedErr *Error expectedErr *Error
expectedWarnings api.Warnings
} }
func (c *testClient) URL(ep string, args map[string]string) *url.URL { func (c *testClient) URL(ep string, args map[string]string) *url.URL {
return nil return nil
} }
func (c *testClient) Do(ctx context.Context, req *http.Request) (*http.Response, []byte, api.Error) { func (c *testClient) Do(ctx context.Context, req *http.Request) (*http.Response, []byte, api.Warnings, error) {
if ctx == nil { if ctx == nil {
c.Fatalf("context was not passed down") c.Fatalf("context was not passed down")
} }
@ -777,7 +837,7 @@ func (c *testClient) Do(ctx context.Context, req *http.Request) (*http.Response,
StatusCode: test.code, StatusCode: test.code,
} }
return resp, b, nil return resp, b, test.expectedWarnings, nil
} }
func TestAPIClientDo(t *testing.T) { func TestAPIClientDo(t *testing.T) {
@ -896,10 +956,10 @@ func TestAPIClientDo(t *testing.T) {
Warnings: []string{"a"}, Warnings: []string{"a"},
}, },
expectedErr: &Error{ expectedErr: &Error{
Type: ErrBadResponse, Type: ErrBadResponse,
Msg: "inconsistent body for response code", Msg: "inconsistent body for response code",
warnings: []string{"a"},
}, },
expectedWarnings: []string{"a"},
}, },
} }
@ -915,7 +975,17 @@ func TestAPIClientDo(t *testing.T) {
tc.ch <- test tc.ch <- test
_, body, err := client.Do(context.Background(), tc.req) _, body, warnings, err := client.Do(context.Background(), tc.req)
if test.expectedWarnings != nil {
if !reflect.DeepEqual(test.expectedWarnings, warnings) {
t.Fatalf("mismatch in warnings expected=%v actual=%v", test.expectedWarnings, warnings)
}
} else {
if warnings != nil {
t.Fatalf("unexpexted warnings: %v", warnings)
}
}
if test.expectedErr != nil { if test.expectedErr != nil {
if err == nil { if err == nil {
@ -933,15 +1003,6 @@ func TestAPIClientDo(t *testing.T) {
} }
} }
if len(test.expectedErr.Warnings()) != len(err.Warnings()) {
t.Fatalf("expected warnings length :%v, but got:%v", len(test.expectedErr.Warnings()), len(err.Warnings()))
}
for x, warning := range test.expectedErr.Warnings() {
if warning != err.Warnings()[x] {
t.Fatalf("expected warning :%v, but got:%v", warning, err.Warnings()[x])
}
}
return return
} }