Add storage.Warnings to client

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

Fixes #560
This commit is contained in:
Thomas Jackson 2019-04-29 13:48:09 -07:00
parent af332dff97
commit d5f3c8d55d
4 changed files with 226 additions and 154 deletions

View File

@ -25,6 +25,42 @@ import (
"time" "time"
) )
func NewErrorAPI(err error, warnings []string) Error {
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{
Proxy: http.ProxyFromEnvironment, Proxy: http.ProxyFromEnvironment,
@ -55,14 +91,14 @@ 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, 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, 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, err return nil, nil, NewErrorAPI(err, nil)
} }
req.Header.Set("Content-Type", "application/x-www-form-urlencoded") req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
@ -71,11 +107,14 @@ func DoGetFallback(c Client, ctx context.Context, u *url.URL, args url.Values) (
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, err return nil, nil, NewErrorAPI(err, nil)
} }
} else { } else {
return resp, body, err if err != nil {
return resp, body, NewErrorAPI(err, nil)
}
return resp, body, nil
} }
return c.Do(ctx, req) return c.Do(ctx, req)
} }
@ -115,7 +154,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, Error) {
if ctx != nil { if ctx != nil {
req = req.WithContext(ctx) req = req.WithContext(ctx)
} }
@ -127,7 +166,7 @@ func (c *httpClient) Do(ctx context.Context, req *http.Request) (*http.Response,
}() }()
if err != nil { if err != nil {
return nil, nil, err return nil, nil, NewErrorAPI(err, nil)
} }
var body []byte var body []byte
@ -147,5 +186,5 @@ func (c *httpClient) Do(ctx context.Context, req *http.Request) (*http.Response,
case <-done: case <-done:
} }
return resp, body, err return resp, body, NewErrorAPI(err, nil)
} }

View File

@ -22,6 +22,7 @@ import (
"fmt" "fmt"
"net/http" "net/http"
"strconv" "strconv"
"strings"
"time" "time"
"github.com/prometheus/client_golang/api" "github.com/prometheus/client_golang/api"
@ -98,10 +99,23 @@ 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.
@ -115,32 +129,32 @@ 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, error) Alerts(ctx context.Context) (AlertsResult, api.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, error) AlertManagers(ctx context.Context) (AlertManagersResult, api.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) error CleanTombstones(ctx context.Context) api.Error
// Config returns the current Prometheus configuration. // Config returns the current Prometheus configuration.
Config(ctx context.Context) (ConfigResult, error) Config(ctx context.Context) (ConfigResult, api.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) error DeleteSeries(ctx context.Context, matches []string, startTime time.Time, endTime time.Time) api.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, error) Flags(ctx context.Context) (FlagsResult, api.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, error) LabelValues(ctx context.Context, label string) (model.LabelValues, api.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, error) Query(ctx context.Context, query string, ts time.Time) (model.Value, api.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, error) QueryRange(ctx context.Context, query string, r Range) (model.Value, api.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, error) Series(ctx context.Context, matches []string, startTime time.Time, endTime time.Time) ([]model.LabelSet, api.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, error) Snapshot(ctx context.Context, skipHead bool) (SnapshotResult, api.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, error) Rules(ctx context.Context) (RulesResult, api.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, error) Targets(ctx context.Context) (TargetsResult, api.Error)
} }
// AlertsResult contains the result from querying the alerts endpoint. // AlertsResult contains the result from querying the alerts endpoint.
@ -408,73 +422,73 @@ type httpAPI struct {
client api.Client client api.Client
} }
func (h *httpAPI) Alerts(ctx context.Context) (AlertsResult, error) { func (h *httpAPI) Alerts(ctx context.Context) (AlertsResult, api.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{}, err return AlertsResult{}, api.NewErrorAPI(err, nil)
} }
_, body, err := h.client.Do(ctx, req) _, body, apiErr := h.client.Do(ctx, req)
if err != nil { if apiErr != nil {
return AlertsResult{}, err return AlertsResult{}, apiErr
} }
var res AlertsResult var res AlertsResult
err = json.Unmarshal(body, &res) err = json.Unmarshal(body, &res)
return res, err return res, api.NewErrorAPI(err, nil)
} }
func (h *httpAPI) AlertManagers(ctx context.Context) (AlertManagersResult, error) { func (h *httpAPI) AlertManagers(ctx context.Context) (AlertManagersResult, api.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{}, err return AlertManagersResult{}, api.NewErrorAPI(err, nil)
} }
_, body, err := h.client.Do(ctx, req) _, body, apiErr := h.client.Do(ctx, req)
if err != nil { if apiErr != nil {
return AlertManagersResult{}, err return AlertManagersResult{}, apiErr
} }
var res AlertManagersResult var res AlertManagersResult
err = json.Unmarshal(body, &res) err = json.Unmarshal(body, &res)
return res, err return res, api.NewErrorAPI(err, nil)
} }
func (h *httpAPI) CleanTombstones(ctx context.Context) error { func (h *httpAPI) CleanTombstones(ctx context.Context) api.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 err return api.NewErrorAPI(err, nil)
} }
_, _, err = h.client.Do(ctx, req) _, _, apiErr := h.client.Do(ctx, req)
return err return apiErr
} }
func (h *httpAPI) Config(ctx context.Context) (ConfigResult, error) { func (h *httpAPI) Config(ctx context.Context) (ConfigResult, api.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{}, err return ConfigResult{}, api.NewErrorAPI(err, nil)
} }
_, body, err := h.client.Do(ctx, req) _, body, apiErr := h.client.Do(ctx, req)
if err != nil { if apiErr != nil {
return ConfigResult{}, err return ConfigResult{}, apiErr
} }
var res ConfigResult var res ConfigResult
err = json.Unmarshal(body, &res) err = json.Unmarshal(body, &res)
return res, err return res, api.NewErrorAPI(err, nil)
} }
func (h *httpAPI) DeleteSeries(ctx context.Context, matches []string, startTime time.Time, endTime time.Time) error { func (h *httpAPI) DeleteSeries(ctx context.Context, matches []string, startTime time.Time, endTime time.Time) api.Error {
u := h.client.URL(epDeleteSeries, nil) u := h.client.URL(epDeleteSeries, nil)
q := u.Query() q := u.Query()
@ -489,47 +503,47 @@ 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 err return api.NewErrorAPI(err, nil)
} }
_, _, err = h.client.Do(ctx, req) _, _, apiErr := h.client.Do(ctx, req)
return err return apiErr
} }
func (h *httpAPI) Flags(ctx context.Context) (FlagsResult, error) { func (h *httpAPI) Flags(ctx context.Context) (FlagsResult, api.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{}, err return FlagsResult{}, api.NewErrorAPI(err, nil)
} }
_, body, err := h.client.Do(ctx, req) _, body, apiErr := h.client.Do(ctx, req)
if err != nil { if apiErr != nil {
return FlagsResult{}, err return FlagsResult{}, apiErr
} }
var res FlagsResult var res FlagsResult
err = json.Unmarshal(body, &res) err = json.Unmarshal(body, &res)
return res, err return res, api.NewErrorAPI(err, nil)
} }
func (h *httpAPI) LabelValues(ctx context.Context, label string) (model.LabelValues, error) { func (h *httpAPI) LabelValues(ctx context.Context, label string) (model.LabelValues, api.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, err return nil, api.NewErrorAPI(err, nil)
} }
_, body, err := h.client.Do(ctx, req) _, body, apiErr := h.client.Do(ctx, req)
if err != nil { if apiErr != nil {
return nil, err return nil, apiErr
} }
var labelValues model.LabelValues var labelValues model.LabelValues
err = json.Unmarshal(body, &labelValues) err = json.Unmarshal(body, &labelValues)
return labelValues, err return labelValues, api.NewErrorAPI(err, nil)
} }
func (h *httpAPI) Query(ctx context.Context, query string, ts time.Time) (model.Value, error) { func (h *httpAPI) Query(ctx context.Context, query string, ts time.Time) (model.Value, api.Error) {
u := h.client.URL(epQuery, nil) u := h.client.URL(epQuery, nil)
q := u.Query() q := u.Query()
@ -538,18 +552,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, err := api.DoGetFallback(h.client, ctx, u, q) _, body, apiErr := api.DoGetFallback(h.client, ctx, u, q)
if err != nil { if apiErr != nil {
return nil, err return nil, apiErr
} }
var qres queryResult var qres queryResult
err = json.Unmarshal(body, &qres) return model.Value(qres.v), api.NewErrorAPI(json.Unmarshal(body, &qres), nil)
return model.Value(qres.v), err
} }
func (h *httpAPI) QueryRange(ctx context.Context, query string, r Range) (model.Value, error) { func (h *httpAPI) QueryRange(ctx context.Context, query string, r Range) (model.Value, api.Error) {
u := h.client.URL(epQueryRange, nil) u := h.client.URL(epQueryRange, nil)
q := u.Query() q := u.Query()
@ -564,18 +576,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, err := api.DoGetFallback(h.client, ctx, u, q) _, body, apiErr := api.DoGetFallback(h.client, ctx, u, q)
if err != nil { if apiErr != nil {
return nil, err return nil, apiErr
} }
var qres queryResult var qres queryResult
err = json.Unmarshal(body, &qres)
return model.Value(qres.v), err return model.Value(qres.v), api.NewErrorAPI(json.Unmarshal(body, &qres), nil)
} }
func (h *httpAPI) Series(ctx context.Context, matches []string, startTime time.Time, endTime time.Time) ([]model.LabelSet, error) { func (h *httpAPI) Series(ctx context.Context, matches []string, startTime time.Time, endTime time.Time) ([]model.LabelSet, api.Error) {
u := h.client.URL(epSeries, nil) u := h.client.URL(epSeries, nil)
q := u.Query() q := u.Query()
@ -590,20 +601,20 @@ 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, err return nil, api.NewErrorAPI(err, nil)
} }
_, body, err := h.client.Do(ctx, req) _, body, apiErr := h.client.Do(ctx, req)
if err != nil { if apiErr != nil {
return nil, err return nil, apiErr
} }
var mset []model.LabelSet var mset []model.LabelSet
err = json.Unmarshal(body, &mset) err = json.Unmarshal(body, &mset)
return mset, err return mset, api.NewErrorAPI(err, nil)
} }
func (h *httpAPI) Snapshot(ctx context.Context, skipHead bool) (SnapshotResult, error) { func (h *httpAPI) Snapshot(ctx context.Context, skipHead bool) (SnapshotResult, api.Error) {
u := h.client.URL(epSnapshot, nil) u := h.client.URL(epSnapshot, nil)
q := u.Query() q := u.Query()
@ -613,53 +624,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{}, err return SnapshotResult{}, api.NewErrorAPI(err, nil)
} }
_, body, err := h.client.Do(ctx, req) _, body, apiErr := h.client.Do(ctx, req)
if err != nil { if apiErr != nil {
return SnapshotResult{}, err return SnapshotResult{}, apiErr
} }
var res SnapshotResult var res SnapshotResult
err = json.Unmarshal(body, &res) err = json.Unmarshal(body, &res)
return res, err return res, api.NewErrorAPI(err, nil)
} }
func (h *httpAPI) Rules(ctx context.Context) (RulesResult, error) { func (h *httpAPI) Rules(ctx context.Context) (RulesResult, api.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{}, err return RulesResult{}, api.NewErrorAPI(err, nil)
} }
_, body, err := h.client.Do(ctx, req) _, body, apiErr := h.client.Do(ctx, req)
if err != nil { if apiErr != nil {
return RulesResult{}, err return RulesResult{}, apiErr
} }
var res RulesResult var res RulesResult
err = json.Unmarshal(body, &res) err = json.Unmarshal(body, &res)
return res, err return res, api.NewErrorAPI(err, nil)
} }
func (h *httpAPI) Targets(ctx context.Context) (TargetsResult, error) { func (h *httpAPI) Targets(ctx context.Context) (TargetsResult, api.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{}, err return TargetsResult{}, api.NewErrorAPI(err, nil)
} }
_, body, err := h.client.Do(ctx, req) _, body, apiErr := h.client.Do(ctx, req)
if err != nil { if apiErr != nil {
return TargetsResult{}, err return TargetsResult{}, apiErr
} }
var res TargetsResult var res TargetsResult
err = json.Unmarshal(body, &res) err = json.Unmarshal(body, &res)
return res, err 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.
@ -673,6 +684,7 @@ type apiResponse struct {
Data json.RawMessage `json:"data"` Data json.RawMessage `json:"data"`
ErrorType ErrorType `json:"errorType"` ErrorType ErrorType `json:"errorType"`
Error string `json:"error"` Error string `json:"error"`
Warnings []string `json:"warnings,omitempty"`
} }
func apiError(code int) bool { func apiError(code int) bool {
@ -690,14 +702,16 @@ 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, error) { func (c apiClient) Do(ctx context.Context, req *http.Request) (*http.Response, []byte, api.Error) {
resp, body, err := c.Client.Do(ctx, req) resp, body, apiErr := c.Client.Do(ctx, req)
if err != nil { if apiErr != nil {
return resp, body, err return resp, body, apiErr
} }
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, &Error{
@ -710,10 +724,10 @@ func (c apiClient) Do(ctx context.Context, req *http.Request) (*http.Response, [
var result apiResponse var result apiResponse
if http.StatusNoContent != code { if http.StatusNoContent != code {
if err = json.Unmarshal(body, &result); err != nil { if jsonErr := json.Unmarshal(body, &result); jsonErr != nil {
return resp, body, &Error{ return resp, body, &Error{
Type: ErrBadResponse, Type: ErrBadResponse,
Msg: err.Error(), Msg: jsonErr.Error(),
} }
} }
} }
@ -722,6 +736,7 @@ func (c apiClient) Do(ctx context.Context, req *http.Request) (*http.Response, [
err = &Error{ err = &Error{
Type: ErrBadResponse, Type: ErrBadResponse,
Msg: "inconsistent body for response code", Msg: "inconsistent body for response code",
warnings: result.Warnings,
} }
} }
@ -729,8 +744,10 @@ func (c apiClient) Do(ctx context.Context, req *http.Request) (*http.Response, [
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), err
} }

View File

@ -25,11 +25,14 @@ import (
"testing" "testing"
"time" "time"
"github.com/prometheus/client_golang/api"
"github.com/prometheus/common/model" "github.com/prometheus/common/model"
"github.com/prometheus/tsdb/testutil"
) )
type apiTest struct { type apiTest struct {
do func() (interface{}, error) do func() (interface{}, api.Error)
inWarnings []string
inErr error inErr error
inStatusCode int inStatusCode int
inRes interface{} inRes interface{}
@ -58,7 +61,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, error) { func (c *apiTestClient) Do(ctx context.Context, req *http.Request) (*http.Response, []byte, api.Error) {
test := c.curTest test := c.curTest
@ -83,7 +86,7 @@ func (c *apiTestClient) Do(ctx context.Context, req *http.Request) (*http.Respon
resp.StatusCode = http.StatusOK resp.StatusCode = http.StatusOK
} }
return resp, b, test.inErr return resp, b, api.NewErrorAPI(test.inErr, test.inWarnings)
} }
func TestAPIs(t *testing.T) { func TestAPIs(t *testing.T) {
@ -96,74 +99,74 @@ func TestAPIs(t *testing.T) {
client: client, client: client,
} }
doAlertManagers := func() func() (interface{}, error) { doAlertManagers := func() func() (interface{}, api.Error) {
return func() (interface{}, error) { return func() (interface{}, api.Error) {
return promAPI.AlertManagers(context.Background()) return promAPI.AlertManagers(context.Background())
} }
} }
doCleanTombstones := func() func() (interface{}, error) { doCleanTombstones := func() func() (interface{}, api.Error) {
return func() (interface{}, error) { return func() (interface{}, api.Error) {
return nil, promAPI.CleanTombstones(context.Background()) return nil, promAPI.CleanTombstones(context.Background())
} }
} }
doConfig := func() func() (interface{}, error) { doConfig := func() func() (interface{}, api.Error) {
return func() (interface{}, error) { return func() (interface{}, api.Error) {
return promAPI.Config(context.Background()) return promAPI.Config(context.Background())
} }
} }
doDeleteSeries := func(matcher string, startTime time.Time, endTime time.Time) func() (interface{}, error) { doDeleteSeries := func(matcher string, startTime time.Time, endTime time.Time) func() (interface{}, api.Error) {
return func() (interface{}, error) { return func() (interface{}, api.Error) {
return nil, promAPI.DeleteSeries(context.Background(), []string{matcher}, startTime, endTime) return nil, promAPI.DeleteSeries(context.Background(), []string{matcher}, startTime, endTime)
} }
} }
doFlags := func() func() (interface{}, error) { doFlags := func() func() (interface{}, api.Error) {
return func() (interface{}, error) { return func() (interface{}, api.Error) {
return promAPI.Flags(context.Background()) return promAPI.Flags(context.Background())
} }
} }
doLabelValues := func(label string) func() (interface{}, error) { doLabelValues := func(label string) func() (interface{}, api.Error) {
return func() (interface{}, error) { return func() (interface{}, api.Error) {
return promAPI.LabelValues(context.Background(), label) return promAPI.LabelValues(context.Background(), label)
} }
} }
doQuery := func(q string, ts time.Time) func() (interface{}, error) { doQuery := func(q string, ts time.Time) func() (interface{}, api.Error) {
return func() (interface{}, error) { return func() (interface{}, api.Error) {
return promAPI.Query(context.Background(), q, ts) return promAPI.Query(context.Background(), q, ts)
} }
} }
doQueryRange := func(q string, rng Range) func() (interface{}, error) { doQueryRange := func(q string, rng Range) func() (interface{}, api.Error) {
return func() (interface{}, error) { return func() (interface{}, api.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{}, error) { doSeries := func(matcher string, startTime time.Time, endTime time.Time) func() (interface{}, api.Error) {
return func() (interface{}, error) { return func() (interface{}, api.Error) {
return promAPI.Series(context.Background(), []string{matcher}, startTime, endTime) return promAPI.Series(context.Background(), []string{matcher}, startTime, endTime)
} }
} }
doSnapshot := func(skipHead bool) func() (interface{}, error) { doSnapshot := func(skipHead bool) func() (interface{}, api.Error) {
return func() (interface{}, error) { return func() (interface{}, api.Error) {
return promAPI.Snapshot(context.Background(), skipHead) return promAPI.Snapshot(context.Background(), skipHead)
} }
} }
doRules := func() func() (interface{}, error) { doRules := func() func() (interface{}, api.Error) {
return func() (interface{}, error) { return func() (interface{}, api.Error) {
return promAPI.Rules(context.Background()) return promAPI.Rules(context.Background())
} }
} }
doTargets := func() func() (interface{}, error) { doTargets := func() func() (interface{}, api.Error) {
return func() (interface{}, error) { return func() (interface{}, api.Error) {
return promAPI.Targets(context.Background()) return promAPI.Targets(context.Background())
} }
} }
@ -693,7 +696,7 @@ 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, error) { func (c *testClient) Do(ctx context.Context, req *http.Request) (*http.Response, []byte, api.Error) {
if ctx == nil { if ctx == nil {
c.Fatalf("context was not passed down") c.Fatalf("context was not passed down")
} }
@ -829,6 +832,21 @@ func TestAPIClientDo(t *testing.T) {
Msg: "inconsistent body for response code", Msg: "inconsistent body for response code",
}, },
}, },
{
code: http.StatusOK,
response: &apiResponse{
Status: "error",
Data: json.RawMessage(`"test"`),
ErrorType: ErrTimeout,
Error: "timed out",
Warnings: []string{"a"},
},
expectedErr: &Error{
Type: ErrBadResponse,
Msg: "inconsistent body for response code",
warnings: []string{"a"},
},
},
} }
tc := &testClient{ tc := &testClient{
@ -846,28 +864,20 @@ func TestAPIClientDo(t *testing.T) {
_, body, err := client.Do(context.Background(), tc.req) _, body, err := client.Do(context.Background(), tc.req)
if test.expectedErr != nil { if test.expectedErr != nil {
if err == nil { testutil.NotOk(t, err)
t.Fatalf("expected error %q but got none", test.expectedErr) testutil.Equals(t, test.expectedErr.Error(), err.Error())
}
if test.expectedErr.Error() != err.Error() {
t.Errorf("unexpected error: want %q, got %q", test.expectedErr, err)
}
if test.expectedErr.Detail != "" { if test.expectedErr.Detail != "" {
apiErr := err.(*Error) apiErr := err.(*Error)
if apiErr.Detail != test.expectedErr.Detail { testutil.Equals(t, apiErr.Detail, test.expectedErr.Detail)
t.Errorf("unexpected error details: want %q, got %q", test.expectedErr.Detail, apiErr.Detail)
}
}
return
}
if err != nil {
t.Fatalf("unexpeceted error %s", err)
} }
want, got := test.expectedBody, string(body) testutil.Equals(t, test.expectedErr.Warnings(), err.Warnings())
if want != got { return
t.Errorf("unexpected body: want %q, got %q", want, got)
} }
testutil.Ok(t, err)
testutil.Equals(t, test.expectedBody, string(body))
}) })
} }

6
go.sum
View File

@ -1,3 +1,4 @@
github.com/OneOfOne/xxhash v1.2.2 h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE=
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
@ -5,7 +6,9 @@ github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973 h1:xJ4a3vCFaGF/jqvzLM
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/beorn7/perks v1.0.0 h1:HWo1m869IqiPhD389kmkxeTalrjNbbJTC8LXupb+sl0= github.com/beorn7/perks v1.0.0 h1:HWo1m869IqiPhD389kmkxeTalrjNbbJTC8LXupb+sl0=
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
github.com/go-kit/kit v0.8.0 h1:Wz+5lgoB0kkuqLEc6NVmwRknTKP6dTGbSqvhZtBI/j0= github.com/go-kit/kit v0.8.0 h1:Wz+5lgoB0kkuqLEc6NVmwRknTKP6dTGbSqvhZtBI/j0=
@ -28,6 +31,7 @@ github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910 h1:idejC8f05m9MGOsuEi1ATq9shN03HrxNkD/luQvxCv8= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910 h1:idejC8f05m9MGOsuEi1ATq9shN03HrxNkD/luQvxCv8=
@ -43,8 +47,10 @@ github.com/prometheus/procfs v0.0.0-20190412120340-e22ddced7142/go.mod h1:TjEm7z
github.com/prometheus/tsdb v0.7.1 h1:YZcsG11NqnK4czYLrWd9mpEuAJIHVQLwdrleYfszMAA= github.com/prometheus/tsdb v0.7.1 h1:YZcsG11NqnK4czYLrWd9mpEuAJIHVQLwdrleYfszMAA=
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72 h1:qLC7fQah7D6K1B0ujays3HV9gkFtllcxhzImRR7ArPQ=
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=