Skip to content

Commit e7776d2

Browse files
authored
Merge pull request #699 from joe-elliott/http-client-warnings
Return Prometheus Warnings
2 parents 99d85f4 + 88792b1 commit e7776d2

File tree

4 files changed

+232
-166
lines changed

4 files changed

+232
-166
lines changed

api/client.go

+4-31
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,6 @@ import (
2525
"time"
2626
)
2727

28-
type Warnings []string
29-
3028
// DefaultRoundTripper is used if no RoundTripper is set in Config.
3129
var DefaultRoundTripper http.RoundTripper = &http.Transport{
3230
Proxy: http.ProxyFromEnvironment,
@@ -57,32 +55,7 @@ func (cfg *Config) roundTripper() http.RoundTripper {
5755
// Client is the interface for an API client.
5856
type Client interface {
5957
URL(ep string, args map[string]string) *url.URL
60-
Do(context.Context, *http.Request) (*http.Response, []byte, Warnings, error)
61-
}
62-
63-
// DoGetFallback will attempt to do the request as-is, and on a 405 it will fallback to a GET request.
64-
func DoGetFallback(c Client, ctx context.Context, u *url.URL, args url.Values) (*http.Response, []byte, Warnings, error) {
65-
req, err := http.NewRequest(http.MethodPost, u.String(), strings.NewReader(args.Encode()))
66-
if err != nil {
67-
return nil, nil, nil, err
68-
}
69-
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
70-
71-
resp, body, warnings, err := c.Do(ctx, req)
72-
if resp != nil && resp.StatusCode == http.StatusMethodNotAllowed {
73-
u.RawQuery = args.Encode()
74-
req, err = http.NewRequest(http.MethodGet, u.String(), nil)
75-
if err != nil {
76-
return nil, nil, warnings, err
77-
}
78-
79-
} else {
80-
if err != nil {
81-
return resp, body, warnings, err
82-
}
83-
return resp, body, warnings, nil
84-
}
85-
return c.Do(ctx, req)
58+
Do(context.Context, *http.Request) (*http.Response, []byte, error)
8659
}
8760

8861
// NewClient returns a new Client.
@@ -120,7 +93,7 @@ func (c *httpClient) URL(ep string, args map[string]string) *url.URL {
12093
return &u
12194
}
12295

123-
func (c *httpClient) Do(ctx context.Context, req *http.Request) (*http.Response, []byte, Warnings, error) {
96+
func (c *httpClient) Do(ctx context.Context, req *http.Request) (*http.Response, []byte, error) {
12497
if ctx != nil {
12598
req = req.WithContext(ctx)
12699
}
@@ -132,7 +105,7 @@ func (c *httpClient) Do(ctx context.Context, req *http.Request) (*http.Response,
132105
}()
133106

134107
if err != nil {
135-
return nil, nil, nil, err
108+
return nil, nil, err
136109
}
137110

138111
var body []byte
@@ -152,5 +125,5 @@ func (c *httpClient) Do(ctx context.Context, req *http.Request) (*http.Response,
152125
case <-done:
153126
}
154127

155-
return resp, body, nil, err
128+
return resp, body, err
156129
}

api/client_test.go

-72
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,7 @@
1414
package api
1515

1616
import (
17-
"context"
18-
"encoding/json"
1917
"net/http"
20-
"net/http/httptest"
2118
"net/url"
2219
"testing"
2320
)
@@ -114,72 +111,3 @@ func TestClientURL(t *testing.T) {
114111
}
115112
}
116113
}
117-
118-
func TestDoGetFallback(t *testing.T) {
119-
v := url.Values{"a": []string{"1", "2"}}
120-
121-
type testResponse struct {
122-
Values string
123-
Method string
124-
}
125-
126-
// Start a local HTTP server.
127-
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
128-
req.ParseForm()
129-
r := &testResponse{
130-
Values: req.Form.Encode(),
131-
Method: req.Method,
132-
}
133-
134-
body, _ := json.Marshal(r)
135-
136-
if req.Method == http.MethodPost {
137-
if req.URL.Path == "/blockPost" {
138-
http.Error(w, string(body), http.StatusMethodNotAllowed)
139-
return
140-
}
141-
}
142-
143-
w.Write(body)
144-
}))
145-
// Close the server when test finishes.
146-
defer server.Close()
147-
148-
u, err := url.Parse(server.URL)
149-
if err != nil {
150-
t.Fatal(err)
151-
}
152-
client := &httpClient{client: *(server.Client())}
153-
154-
// Do a post, and ensure that the post succeeds.
155-
_, b, _, err := DoGetFallback(client, context.TODO(), u, v)
156-
if err != nil {
157-
t.Fatalf("Error doing local request: %v", err)
158-
}
159-
resp := &testResponse{}
160-
if err := json.Unmarshal(b, resp); err != nil {
161-
t.Fatal(err)
162-
}
163-
if resp.Method != http.MethodPost {
164-
t.Fatalf("Mismatch method")
165-
}
166-
if resp.Values != v.Encode() {
167-
t.Fatalf("Mismatch in values")
168-
}
169-
170-
// Do a fallbcak to a get.
171-
u.Path = "/blockPost"
172-
_, b, _, err = DoGetFallback(client, context.TODO(), u, v)
173-
if err != nil {
174-
t.Fatalf("Error doing local request: %v", err)
175-
}
176-
if err := json.Unmarshal(b, resp); err != nil {
177-
t.Fatal(err)
178-
}
179-
if resp.Method != http.MethodGet {
180-
t.Fatalf("Mismatch method")
181-
}
182-
if resp.Values != v.Encode() {
183-
t.Fatalf("Mismatch in values")
184-
}
185-
}

api/prometheus/v1/api.go

+66-22
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,9 @@ import (
2121
"fmt"
2222
"math"
2323
"net/http"
24+
"net/url"
2425
"strconv"
26+
"strings"
2527
"time"
2628
"unsafe"
2729

@@ -228,15 +230,15 @@ type API interface {
228230
// Flags returns the flag values that Prometheus was launched with.
229231
Flags(ctx context.Context) (FlagsResult, error)
230232
// LabelNames returns all the unique label names present in the block in sorted order.
231-
LabelNames(ctx context.Context) ([]string, api.Warnings, error)
233+
LabelNames(ctx context.Context) ([]string, Warnings, error)
232234
// LabelValues performs a query for the values of the given label.
233-
LabelValues(ctx context.Context, label string) (model.LabelValues, api.Warnings, error)
235+
LabelValues(ctx context.Context, label string) (model.LabelValues, Warnings, error)
234236
// Query performs a query for the given time.
235-
Query(ctx context.Context, query string, ts time.Time) (model.Value, api.Warnings, error)
237+
Query(ctx context.Context, query string, ts time.Time) (model.Value, Warnings, error)
236238
// QueryRange performs a query for the given range.
237-
QueryRange(ctx context.Context, query string, r Range) (model.Value, api.Warnings, error)
239+
QueryRange(ctx context.Context, query string, r Range) (model.Value, Warnings, error)
238240
// Series finds series by label matchers.
239-
Series(ctx context.Context, matches []string, startTime time.Time, endTime time.Time) ([]model.LabelSet, api.Warnings, error)
241+
Series(ctx context.Context, matches []string, startTime time.Time, endTime time.Time) ([]model.LabelSet, Warnings, error)
240242
// Snapshot creates a snapshot of all current data into snapshots/<datetime>-<rand>
241243
// under the TSDB's data directory and returns the directory as response.
242244
Snapshot(ctx context.Context, skipHead bool) (SnapshotResult, error)
@@ -515,11 +517,15 @@ func (qr *queryResult) UnmarshalJSON(b []byte) error {
515517
//
516518
// It is safe to use the returned API from multiple goroutines.
517519
func NewAPI(c api.Client) API {
518-
return &httpAPI{client: apiClient{c}}
520+
return &httpAPI{
521+
client: &apiClientImpl{
522+
client: c,
523+
},
524+
}
519525
}
520526

521527
type httpAPI struct {
522-
client api.Client
528+
client apiClient
523529
}
524530

525531
func (h *httpAPI) Alerts(ctx context.Context) (AlertsResult, error) {
@@ -624,7 +630,7 @@ func (h *httpAPI) Flags(ctx context.Context) (FlagsResult, error) {
624630
return res, json.Unmarshal(body, &res)
625631
}
626632

627-
func (h *httpAPI) LabelNames(ctx context.Context) ([]string, api.Warnings, error) {
633+
func (h *httpAPI) LabelNames(ctx context.Context) ([]string, Warnings, error) {
628634
u := h.client.URL(epLabels, nil)
629635
req, err := http.NewRequest(http.MethodGet, u.String(), nil)
630636
if err != nil {
@@ -638,7 +644,7 @@ func (h *httpAPI) LabelNames(ctx context.Context) ([]string, api.Warnings, error
638644
return labelNames, w, json.Unmarshal(body, &labelNames)
639645
}
640646

641-
func (h *httpAPI) LabelValues(ctx context.Context, label string) (model.LabelValues, api.Warnings, error) {
647+
func (h *httpAPI) LabelValues(ctx context.Context, label string) (model.LabelValues, Warnings, error) {
642648
u := h.client.URL(epLabelValues, map[string]string{"name": label})
643649
req, err := http.NewRequest(http.MethodGet, u.String(), nil)
644650
if err != nil {
@@ -652,7 +658,7 @@ func (h *httpAPI) LabelValues(ctx context.Context, label string) (model.LabelVal
652658
return labelValues, w, json.Unmarshal(body, &labelValues)
653659
}
654660

655-
func (h *httpAPI) Query(ctx context.Context, query string, ts time.Time) (model.Value, api.Warnings, error) {
661+
func (h *httpAPI) Query(ctx context.Context, query string, ts time.Time) (model.Value, Warnings, error) {
656662
u := h.client.URL(epQuery, nil)
657663
q := u.Query()
658664

@@ -661,7 +667,7 @@ func (h *httpAPI) Query(ctx context.Context, query string, ts time.Time) (model.
661667
q.Set("time", formatTime(ts))
662668
}
663669

664-
_, body, warnings, err := api.DoGetFallback(h.client, ctx, u, q)
670+
_, body, warnings, err := h.client.DoGetFallback(ctx, u, q)
665671
if err != nil {
666672
return nil, warnings, err
667673
}
@@ -670,7 +676,7 @@ func (h *httpAPI) Query(ctx context.Context, query string, ts time.Time) (model.
670676
return model.Value(qres.v), warnings, json.Unmarshal(body, &qres)
671677
}
672678

673-
func (h *httpAPI) QueryRange(ctx context.Context, query string, r Range) (model.Value, api.Warnings, error) {
679+
func (h *httpAPI) QueryRange(ctx context.Context, query string, r Range) (model.Value, Warnings, error) {
674680
u := h.client.URL(epQueryRange, nil)
675681
q := u.Query()
676682

@@ -679,7 +685,7 @@ func (h *httpAPI) QueryRange(ctx context.Context, query string, r Range) (model.
679685
q.Set("end", formatTime(r.End))
680686
q.Set("step", strconv.FormatFloat(r.Step.Seconds(), 'f', -1, 64))
681687

682-
_, body, warnings, err := api.DoGetFallback(h.client, ctx, u, q)
688+
_, body, warnings, err := h.client.DoGetFallback(ctx, u, q)
683689
if err != nil {
684690
return nil, warnings, err
685691
}
@@ -689,7 +695,7 @@ func (h *httpAPI) QueryRange(ctx context.Context, query string, r Range) (model.
689695
return model.Value(qres.v), warnings, json.Unmarshal(body, &qres)
690696
}
691697

692-
func (h *httpAPI) Series(ctx context.Context, matches []string, startTime time.Time, endTime time.Time) ([]model.LabelSet, api.Warnings, error) {
698+
func (h *httpAPI) Series(ctx context.Context, matches []string, startTime time.Time, endTime time.Time) ([]model.LabelSet, Warnings, error) {
693699
u := h.client.URL(epSeries, nil)
694700
q := u.Query()
695701

@@ -796,10 +802,19 @@ func (h *httpAPI) TargetsMetadata(ctx context.Context, matchTarget string, metri
796802
return res, json.Unmarshal(body, &res)
797803
}
798804

805+
// Warnings is an array of non critical errors
806+
type Warnings []string
807+
799808
// apiClient wraps a regular client and processes successful API responses.
800809
// Successful also includes responses that errored at the API level.
801-
type apiClient struct {
802-
api.Client
810+
type apiClient interface {
811+
URL(ep string, args map[string]string) *url.URL
812+
Do(context.Context, *http.Request) (*http.Response, []byte, Warnings, error)
813+
DoGetFallback(ctx context.Context, u *url.URL, args url.Values) (*http.Response, []byte, Warnings, error)
814+
}
815+
816+
type apiClientImpl struct {
817+
client api.Client
803818
}
804819

805820
type apiResponse struct {
@@ -825,17 +840,21 @@ func errorTypeAndMsgFor(resp *http.Response) (ErrorType, string) {
825840
return ErrBadResponse, fmt.Sprintf("bad response code %d", resp.StatusCode)
826841
}
827842

828-
func (c apiClient) Do(ctx context.Context, req *http.Request) (*http.Response, []byte, api.Warnings, error) {
829-
resp, body, warnings, err := c.Client.Do(ctx, req)
843+
func (h *apiClientImpl) URL(ep string, args map[string]string) *url.URL {
844+
return h.client.URL(ep, args)
845+
}
846+
847+
func (h *apiClientImpl) Do(ctx context.Context, req *http.Request) (*http.Response, []byte, Warnings, error) {
848+
resp, body, err := h.client.Do(ctx, req)
830849
if err != nil {
831-
return resp, body, warnings, err
850+
return resp, body, nil, err
832851
}
833852

834853
code := resp.StatusCode
835854

836855
if code/100 != 2 && !apiError(code) {
837856
errorType, errorMsg := errorTypeAndMsgFor(resp)
838-
return resp, body, warnings, &Error{
857+
return resp, body, nil, &Error{
839858
Type: errorType,
840859
Msg: errorMsg,
841860
Detail: string(body),
@@ -846,7 +865,7 @@ func (c apiClient) Do(ctx context.Context, req *http.Request) (*http.Response, [
846865

847866
if http.StatusNoContent != code {
848867
if jsonErr := json.Unmarshal(body, &result); jsonErr != nil {
849-
return resp, body, warnings, &Error{
868+
return resp, body, nil, &Error{
850869
Type: ErrBadResponse,
851870
Msg: jsonErr.Error(),
852871
}
@@ -867,10 +886,35 @@ func (c apiClient) Do(ctx context.Context, req *http.Request) (*http.Response, [
867886
}
868887
}
869888

870-
return resp, []byte(result.Data), warnings, err
889+
return resp, []byte(result.Data), result.Warnings, err
871890

872891
}
873892

893+
// DoGetFallback will attempt to do the request as-is, and on a 405 it will fallback to a GET request.
894+
func (h *apiClientImpl) DoGetFallback(ctx context.Context, u *url.URL, args url.Values) (*http.Response, []byte, Warnings, error) {
895+
req, err := http.NewRequest(http.MethodPost, u.String(), strings.NewReader(args.Encode()))
896+
if err != nil {
897+
return nil, nil, nil, err
898+
}
899+
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
900+
901+
resp, body, warnings, err := h.Do(ctx, req)
902+
if resp != nil && resp.StatusCode == http.StatusMethodNotAllowed {
903+
u.RawQuery = args.Encode()
904+
req, err = http.NewRequest(http.MethodGet, u.String(), nil)
905+
if err != nil {
906+
return nil, nil, warnings, err
907+
}
908+
909+
} else {
910+
if err != nil {
911+
return resp, body, warnings, err
912+
}
913+
return resp, body, warnings, nil
914+
}
915+
return h.Do(ctx, req)
916+
}
917+
874918
func formatTime(t time.Time) string {
875919
return strconv.FormatFloat(float64(t.Unix())+float64(t.Nanosecond())/1e9, 'f', -1, 64)
876920
}

0 commit comments

Comments
 (0)