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"
)
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
}
type Warnings []string
// DefaultRoundTripper is used if no RoundTripper is set in Config.
var DefaultRoundTripper http.RoundTripper = &http.Transport{
@ -91,30 +57,30 @@ func (cfg *Config) roundTripper() http.RoundTripper {
// Client is the interface for an API client.
type Client interface {
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.
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()))
if err != nil {
return nil, nil, NewErrorAPI(err, nil)
return nil, nil, nil, err
}
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 {
u.RawQuery = args.Encode()
req, err = http.NewRequest(http.MethodGet, u.String(), nil)
if err != nil {
return nil, nil, NewErrorAPI(err, nil)
return nil, nil, warnings, err
}
} else {
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)
}
@ -154,7 +120,7 @@ func (c *httpClient) URL(ep string, args map[string]string) *url.URL {
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 {
req = req.WithContext(ctx)
}
@ -166,7 +132,7 @@ func (c *httpClient) Do(ctx context.Context, req *http.Request) (*http.Response,
}()
if err != nil {
return nil, nil, NewErrorAPI(err, nil)
return nil, nil, nil, err
}
var body []byte
@ -186,5 +152,5 @@ func (c *httpClient) Do(ctx context.Context, req *http.Request) (*http.Response,
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())}
// 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 {
t.Fatalf("Error doing local request: %v", err)
}
@ -169,7 +169,7 @@ func TestDoGetFallback(t *testing.T) {
// Do a fallbcak to a get.
u.Path = "/blockPost"
_, b, err = DoGetFallback(client, context.TODO(), u, v)
_, b, _, err = DoGetFallback(client, context.TODO(), u, v)
if err != nil {
t.Fatalf("Error doing local request: %v", err)
}

View File

@ -22,7 +22,6 @@ import (
"math"
"net/http"
"strconv"
"strings"
"time"
"unsafe"
@ -200,25 +199,12 @@ type Error struct {
Type ErrorType
Msg string
Detail string
warnings []string
}
func (e *Error) Error() string {
if 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.
type Range struct {
// The boundaries of the time range.
@ -230,34 +216,34 @@ type Range struct {
// API provides bindings for Prometheus's v1 API.
type API interface {
// 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(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(ctx context.Context) api.Error
CleanTombstones(ctx context.Context) error
// 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(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(ctx context.Context) (FlagsResult, api.Error)
Flags(ctx context.Context) (FlagsResult, error)
// 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(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(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(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>
// 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(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(ctx context.Context) (TargetsResult, api.Error)
Targets(ctx context.Context) (TargetsResult, error)
// 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.
@ -534,73 +520,70 @@ type httpAPI struct {
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)
req, err := http.NewRequest(http.MethodGet, u.String(), nil)
if err != nil {
return AlertsResult{}, api.NewErrorAPI(err, nil)
return AlertsResult{}, err
}
_, body, apiErr := h.client.Do(ctx, req)
if apiErr != nil {
return AlertsResult{}, apiErr
_, body, _, err := h.client.Do(ctx, req)
if err != nil {
return AlertsResult{}, err
}
var res AlertsResult
err = json.Unmarshal(body, &res)
return res, api.NewErrorAPI(err, nil)
return res, json.Unmarshal(body, &res)
}
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)
req, err := http.NewRequest(http.MethodGet, u.String(), nil)
if err != nil {
return AlertManagersResult{}, api.NewErrorAPI(err, nil)
return AlertManagersResult{}, err
}
_, body, apiErr := h.client.Do(ctx, req)
if apiErr != nil {
return AlertManagersResult{}, apiErr
_, body, _, err := h.client.Do(ctx, req)
if err != nil {
return AlertManagersResult{}, err
}
var res AlertManagersResult
err = json.Unmarshal(body, &res)
return res, api.NewErrorAPI(err, nil)
return res, json.Unmarshal(body, &res)
}
func (h *httpAPI) CleanTombstones(ctx context.Context) api.Error {
func (h *httpAPI) CleanTombstones(ctx context.Context) error {
u := h.client.URL(epCleanTombstones, nil)
req, err := http.NewRequest(http.MethodPost, u.String(), nil)
if err != nil {
return api.NewErrorAPI(err, nil)
return err
}
_, _, apiErr := h.client.Do(ctx, req)
return apiErr
_, _, _, err = h.client.Do(ctx, req)
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)
req, err := http.NewRequest(http.MethodGet, u.String(), nil)
if err != nil {
return ConfigResult{}, api.NewErrorAPI(err, nil)
return ConfigResult{}, err
}
_, body, apiErr := h.client.Do(ctx, req)
if apiErr != nil {
return ConfigResult{}, apiErr
_, body, _, err := h.client.Do(ctx, req)
if err != nil {
return ConfigResult{}, err
}
var res ConfigResult
err = json.Unmarshal(body, &res)
return res, api.NewErrorAPI(err, nil)
return res, json.Unmarshal(body, &res)
}
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)
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)
if err != nil {
return api.NewErrorAPI(err, nil)
return err
}
_, _, apiErr := h.client.Do(ctx, req)
return apiErr
_, _, _, err = h.client.Do(ctx, req)
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)
req, err := http.NewRequest(http.MethodGet, u.String(), nil)
if err != nil {
return FlagsResult{}, api.NewErrorAPI(err, nil)
return FlagsResult{}, err
}
_, body, apiErr := h.client.Do(ctx, req)
if apiErr != nil {
return FlagsResult{}, apiErr
_, body, _, err := h.client.Do(ctx, req)
if err != nil {
return FlagsResult{}, err
}
var res FlagsResult
err = json.Unmarshal(body, &res)
return res, api.NewErrorAPI(err, nil)
return res, json.Unmarshal(body, &res)
}
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})
req, err := http.NewRequest(http.MethodGet, u.String(), nil)
if err != nil {
return nil, api.NewErrorAPI(err, nil)
return nil, err
}
_, body, apiErr := h.client.Do(ctx, req)
if apiErr != nil {
return nil, apiErr
_, body, _, err := h.client.Do(ctx, req)
if err != nil {
return nil, err
}
var labelValues model.LabelValues
err = json.Unmarshal(body, &labelValues)
return labelValues, api.NewErrorAPI(err, nil)
return labelValues, json.Unmarshal(body, &labelValues)
}
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)
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))
}
_, body, apiErr := api.DoGetFallback(h.client, ctx, u, q)
if apiErr != nil {
return nil, apiErr
_, body, warnings, err := api.DoGetFallback(h.client, ctx, u, q)
if err != nil {
return nil, warnings, err
}
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)
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("step", step)
_, body, apiErr := api.DoGetFallback(h.client, ctx, u, q)
if apiErr != nil {
return nil, apiErr
_, body, warnings, err := api.DoGetFallback(h.client, ctx, u, q)
if err != nil {
return nil, warnings, err
}
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)
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)
if err != nil {
return nil, api.NewErrorAPI(err, nil)
return nil, err
}
_, body, apiErr := h.client.Do(ctx, req)
if apiErr != nil {
return nil, apiErr
_, body, _, err := h.client.Do(ctx, req)
if err != nil {
return nil, err
}
var mset []model.LabelSet
err = json.Unmarshal(body, &mset)
return mset, api.NewErrorAPI(err, nil)
return mset, json.Unmarshal(body, &mset)
}
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)
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)
if err != nil {
return SnapshotResult{}, api.NewErrorAPI(err, nil)
return SnapshotResult{}, err
}
_, body, apiErr := h.client.Do(ctx, req)
if apiErr != nil {
return SnapshotResult{}, apiErr
_, body, _, err := h.client.Do(ctx, req)
if err != nil {
return SnapshotResult{}, err
}
var res SnapshotResult
err = json.Unmarshal(body, &res)
return res, api.NewErrorAPI(err, nil)
return res, json.Unmarshal(body, &res)
}
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)
req, err := http.NewRequest(http.MethodGet, u.String(), nil)
if err != nil {
return RulesResult{}, api.NewErrorAPI(err, nil)
return RulesResult{}, err
}
_, body, apiErr := h.client.Do(ctx, req)
if apiErr != nil {
return RulesResult{}, apiErr
_, body, _, err := h.client.Do(ctx, req)
if err != nil {
return RulesResult{}, err
}
var res RulesResult
err = json.Unmarshal(body, &res)
return res, api.NewErrorAPI(err, nil)
return res, json.Unmarshal(body, &res)
}
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)
req, err := http.NewRequest(http.MethodGet, u.String(), nil)
if err != nil {
return TargetsResult{}, api.NewErrorAPI(err, nil)
return TargetsResult{}, err
}
_, body, apiErr := h.client.Do(ctx, req)
if apiErr != nil {
return TargetsResult{}, apiErr
_, body, _, err := h.client.Do(ctx, req)
if err != nil {
return TargetsResult{}, err
}
var res TargetsResult
err = json.Unmarshal(body, &res)
return res, api.NewErrorAPI(err, nil)
return res, json.Unmarshal(body, &res)
}
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)
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)
if err != nil {
return nil, api.NewErrorAPI(err, nil)
return nil, err
}
_, body, apiErr := h.client.Do(ctx, req)
if apiErr != nil {
return nil, apiErr
_, body, _, err := h.client.Do(ctx, req)
if err != nil {
return nil, err
}
var res []MetricMetadata
err = json.Unmarshal(body, &res)
return res, api.NewErrorAPI(err, nil)
return res, json.Unmarshal(body, &res)
}
// 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)
}
func (c apiClient) Do(ctx context.Context, req *http.Request) (*http.Response, []byte, api.Error) {
resp, body, apiErr := c.Client.Do(ctx, req)
if apiErr != nil {
return resp, body, apiErr
func (c apiClient) Do(ctx context.Context, req *http.Request) (*http.Response, []byte, api.Warnings, error) {
resp, body, warnings, err := c.Client.Do(ctx, req)
if err != nil {
return resp, body, warnings, err
}
code := resp.StatusCode
var err api.Error
if code/100 != 2 && !apiError(code) {
errorType, errorMsg := errorTypeAndMsgFor(resp)
return resp, body, &Error{
return resp, body, warnings, &Error{
Type: errorType,
Msg: errorMsg,
Detail: string(body),
@ -862,7 +836,7 @@ func (c apiClient) Do(ctx context.Context, req *http.Request) (*http.Response, [
if http.StatusNoContent != code {
if jsonErr := json.Unmarshal(body, &result); jsonErr != nil {
return resp, body, &Error{
return resp, body, warnings, &Error{
Type: ErrBadResponse,
Msg: jsonErr.Error(),
}
@ -873,7 +847,6 @@ func (c apiClient) Do(ctx context.Context, req *http.Request) (*http.Response, [
err = &Error{
Type: ErrBadResponse,
Msg: "inconsistent body for response code",
warnings: result.Warnings,
}
}
@ -881,10 +854,9 @@ func (c apiClient) Do(ctx context.Context, req *http.Request) (*http.Response, [
err = &Error{
Type: result.ErrorType,
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 {
do func() (interface{}, api.Error)
do func() (interface{}, api.Warnings, error)
inWarnings []string
inErr error
inStatusCode int
@ -43,6 +43,7 @@ type apiTest struct {
reqParam url.Values
reqMethod string
res interface{}
warnings api.Warnings
err error
}
@ -63,7 +64,7 @@ func (c *apiTestClient) URL(ep string, args map[string]string) *url.URL {
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
@ -88,7 +89,7 @@ func (c *apiTestClient) Do(ctx context.Context, req *http.Request) (*http.Respon
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) {
@ -101,81 +102,90 @@ func TestAPIs(t *testing.T) {
client: client,
}
doAlertManagers := func() func() (interface{}, api.Error) {
return func() (interface{}, api.Error) {
return promAPI.AlertManagers(context.Background())
doAlertManagers := func() func() (interface{}, api.Warnings, error) {
return func() (interface{}, api.Warnings, error) {
v, err := promAPI.AlertManagers(context.Background())
return v, nil, err
}
}
doCleanTombstones := func() func() (interface{}, api.Error) {
return func() (interface{}, api.Error) {
return nil, promAPI.CleanTombstones(context.Background())
doCleanTombstones := func() func() (interface{}, api.Warnings, error) {
return func() (interface{}, api.Warnings, error) {
return nil, nil, promAPI.CleanTombstones(context.Background())
}
}
doConfig := func() func() (interface{}, api.Error) {
return func() (interface{}, api.Error) {
return promAPI.Config(context.Background())
doConfig := func() func() (interface{}, api.Warnings, error) {
return func() (interface{}, api.Warnings, error) {
v, err := promAPI.Config(context.Background())
return v, nil, err
}
}
doDeleteSeries := func(matcher string, startTime time.Time, endTime time.Time) func() (interface{}, api.Error) {
return func() (interface{}, api.Error) {
return nil, promAPI.DeleteSeries(context.Background(), []string{matcher}, startTime, endTime)
doDeleteSeries := func(matcher string, startTime time.Time, endTime time.Time) func() (interface{}, api.Warnings, error) {
return func() (interface{}, api.Warnings, error) {
return nil, nil, promAPI.DeleteSeries(context.Background(), []string{matcher}, startTime, endTime)
}
}
doFlags := func() func() (interface{}, api.Error) {
return func() (interface{}, api.Error) {
return promAPI.Flags(context.Background())
doFlags := func() func() (interface{}, api.Warnings, error) {
return func() (interface{}, api.Warnings, error) {
v, err := promAPI.Flags(context.Background())
return v, nil, err
}
}
doLabelValues := func(label string) func() (interface{}, api.Error) {
return func() (interface{}, api.Error) {
return promAPI.LabelValues(context.Background(), label)
doLabelValues := func(label string) func() (interface{}, api.Warnings, error) {
return func() (interface{}, api.Warnings, error) {
v, err := promAPI.LabelValues(context.Background(), label)
return v, nil, err
}
}
doQuery := func(q string, ts time.Time) func() (interface{}, api.Error) {
return func() (interface{}, api.Error) {
doQuery := func(q string, ts time.Time) func() (interface{}, api.Warnings, error) {
return func() (interface{}, api.Warnings, error) {
return promAPI.Query(context.Background(), q, ts)
}
}
doQueryRange := func(q string, rng Range) func() (interface{}, api.Error) {
return func() (interface{}, api.Error) {
doQueryRange := func(q string, rng Range) func() (interface{}, api.Warnings, error) {
return func() (interface{}, api.Warnings, error) {
return promAPI.QueryRange(context.Background(), q, rng)
}
}
doSeries := func(matcher string, startTime time.Time, endTime time.Time) func() (interface{}, api.Error) {
return func() (interface{}, api.Error) {
return promAPI.Series(context.Background(), []string{matcher}, startTime, endTime)
doSeries := func(matcher string, startTime time.Time, endTime time.Time) func() (interface{}, api.Warnings, error) {
return func() (interface{}, api.Warnings, error) {
v, err := promAPI.Series(context.Background(), []string{matcher}, startTime, endTime)
return v, nil, err
}
}
doSnapshot := func(skipHead bool) func() (interface{}, api.Error) {
return func() (interface{}, api.Error) {
return promAPI.Snapshot(context.Background(), skipHead)
doSnapshot := func(skipHead bool) func() (interface{}, api.Warnings, error) {
return func() (interface{}, api.Warnings, error) {
v, err := promAPI.Snapshot(context.Background(), skipHead)
return v, nil, err
}
}
doRules := func() func() (interface{}, api.Error) {
return func() (interface{}, api.Error) {
return promAPI.Rules(context.Background())
doRules := func() func() (interface{}, api.Warnings, error) {
return func() (interface{}, api.Warnings, error) {
v, err := promAPI.Rules(context.Background())
return v, nil, err
}
}
doTargets := func() func() (interface{}, api.Error) {
return func() (interface{}, api.Error) {
return promAPI.Targets(context.Background())
doTargets := func() func() (interface{}, api.Warnings, error) {
return func() (interface{}, api.Warnings, error) {
v, err := promAPI.Targets(context.Background())
return v, nil, err
}
}
doTargetsMetadata := func(matchTarget string, metric string, limit string) func() (interface{}, api.Error) {
return func() (interface{}, api.Error) {
return promAPI.TargetsMetadata(context.Background(), matchTarget, metric, limit)
doTargetsMetadata := func(matchTarget string, metric string, limit string) func() (interface{}, api.Warnings, error) {
return func() (interface{}, api.Warnings, error) {
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"),
},
// 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{
@ -705,7 +760,11 @@ func TestAPIs(t *testing.T) {
t.Run(fmt.Sprintf("%d", i), func(t *testing.T) {
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 err == nil {
@ -744,13 +803,14 @@ type apiClientTest struct {
response interface{}
expectedBody string
expectedErr *Error
expectedWarnings api.Warnings
}
func (c *testClient) URL(ep string, args map[string]string) *url.URL {
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 {
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,
}
return resp, b, nil
return resp, b, test.expectedWarnings, nil
}
func TestAPIClientDo(t *testing.T) {
@ -898,8 +958,8 @@ func TestAPIClientDo(t *testing.T) {
expectedErr: &Error{
Type: ErrBadResponse,
Msg: "inconsistent body for response code",
warnings: []string{"a"},
},
expectedWarnings: []string{"a"},
},
}
@ -915,7 +975,17 @@ func TestAPIClientDo(t *testing.T) {
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 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
}