Skip to content

Commit b804e17

Browse files
committed
temp
1 parent 0d621ff commit b804e17

File tree

11 files changed

+234
-131
lines changed

11 files changed

+234
-131
lines changed

pkg/api/queryapi/query_api.go

Lines changed: 64 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -2,23 +2,21 @@ package queryapi
22

33
import (
44
"context"
5+
"errors"
56
"fmt"
67
"net/http"
78
"time"
89

910
"github.com/go-kit/log"
1011
"github.com/go-kit/log/level"
1112
"github.com/grafana/regexp"
13+
jsoniter "github.com/json-iterator/go"
1214
"github.com/munnerz/goautoneg"
1315
"github.com/prometheus/prometheus/promql"
1416
"github.com/prometheus/prometheus/storage"
1517
"github.com/prometheus/prometheus/util/annotations"
1618
"github.com/prometheus/prometheus/util/httputil"
1719
v1 "github.com/prometheus/prometheus/web/api/v1"
18-
"github.com/weaveworks/common/httpgrpc"
19-
20-
"github.com/cortexproject/cortex/pkg/util"
21-
"github.com/cortexproject/cortex/pkg/util/api"
2220
)
2321

2422
type QueryAPI struct {
@@ -51,53 +49,54 @@ func NewQueryAPI(
5149
}
5250

5351
func (q *QueryAPI) RangeQueryHandler(r *http.Request) (result apiFuncResult) {
54-
// TODO(Sungjin1212): Change to emit basic error (not gRPC)
55-
start, err := util.ParseTime(r.FormValue("start"))
52+
start, err := ParseTime(r.FormValue("start"))
5653
if err != nil {
5754
return invalidParamError(err, "start")
5855
}
59-
end, err := util.ParseTime(r.FormValue("end"))
56+
end, err := ParseTime(r.FormValue("end"))
6057
if err != nil {
6158
return invalidParamError(err, "end")
6259
}
63-
if end < start {
64-
return invalidParamError(ErrEndBeforeStart, "end")
60+
61+
if end.Before(start) {
62+
return invalidParamError(errors.New("end timestamp must not be before start time"), "end")
6563
}
6664

67-
step, err := util.ParseDurationMs(r.FormValue("step"))
65+
step, err := ParseDuration(r.FormValue("step"))
6866
if err != nil {
6967
return invalidParamError(err, "step")
7068
}
7169

7270
if step <= 0 {
73-
return invalidParamError(ErrNegativeStep, "step")
71+
return invalidParamError(errors.New("zero or negative query resolution step widths are not accepted. Try a positive integer"), "step")
7472
}
7573

7674
// For safety, limit the number of returned points per timeseries.
7775
// This is sufficient for 60s resolution for a week or 1h resolution for a year.
78-
if (end-start)/step > 11000 {
79-
return apiFuncResult{nil, &apiError{errorBadData, ErrStepTooSmall}, nil, nil}
76+
if end.Sub(start)/step > 11000 {
77+
err := errors.New("exceeded maximum resolution of 11,000 points per timeseries. Try decreasing the query resolution (?step=XX)")
78+
return apiFuncResult{nil, &apiError{errorBadData, err}, nil, nil}
8079
}
8180

8281
ctx := r.Context()
8382
if to := r.FormValue("timeout"); to != "" {
8483
var cancel context.CancelFunc
85-
timeout, err := util.ParseDurationMs(to)
84+
timeout, err := ParseDuration(to)
8685
if err != nil {
8786
return invalidParamError(err, "timeout")
8887
}
8988

90-
ctx, cancel = context.WithTimeout(ctx, convertMsToDuration(timeout))
89+
ctx, cancel = context.WithTimeout(ctx, timeout)
9190
defer cancel()
9291
}
9392

9493
opts, err := extractQueryOpts(r)
9594
if err != nil {
9695
return apiFuncResult{nil, &apiError{errorBadData, err}, nil, nil}
9796
}
98-
qry, err := q.queryEngine.NewRangeQuery(ctx, q.queryable, opts, r.FormValue("query"), convertMsToTime(start), convertMsToTime(end), convertMsToDuration(step))
97+
qry, err := q.queryEngine.NewRangeQuery(ctx, q.queryable, opts, r.FormValue("query"), start, end, step)
9998
if err != nil {
100-
return invalidParamError(httpgrpc.Errorf(http.StatusBadRequest, "%s", err.Error()), "query")
99+
return invalidParamError(err, "query")
101100
}
102101
// From now on, we must only return with a finalizer in the result (to
103102
// be called by the caller) or call qry.Close ourselves (which is
@@ -126,31 +125,30 @@ func (q *QueryAPI) RangeQueryHandler(r *http.Request) (result apiFuncResult) {
126125
}
127126

128127
func (q *QueryAPI) InstantQueryHandler(r *http.Request) (result apiFuncResult) {
129-
// TODO(Sungjin1212): Change to emit basic error (not gRPC)
130-
ts, err := util.ParseTimeParam(r, "time", q.now().Unix())
128+
ts, err := ParseTimeParam(r, "time", q.now())
131129
if err != nil {
132130
return invalidParamError(err, "time")
133131
}
134132

135133
ctx := r.Context()
136134
if to := r.FormValue("timeout"); to != "" {
137135
var cancel context.CancelFunc
138-
timeout, err := util.ParseDurationMs(to)
136+
timeout, err := ParseDuration(to)
139137
if err != nil {
140138
return invalidParamError(err, "timeout")
141139
}
142140

143-
ctx, cancel = context.WithDeadline(ctx, q.now().Add(convertMsToDuration(timeout)))
141+
ctx, cancel = context.WithDeadline(ctx, q.now().Add(timeout))
144142
defer cancel()
145143
}
146144

147145
opts, err := extractQueryOpts(r)
148146
if err != nil {
149147
return apiFuncResult{nil, &apiError{errorBadData, err}, nil, nil}
150148
}
151-
qry, err := q.queryEngine.NewInstantQuery(ctx, q.queryable, opts, r.FormValue("query"), convertMsToTime(ts))
149+
qry, err := q.queryEngine.NewInstantQuery(ctx, q.queryable, opts, r.FormValue("query"), ts)
152150
if err != nil {
153-
return invalidParamError(httpgrpc.Errorf(http.StatusBadRequest, "%s", err.Error()), "query")
151+
return invalidParamError(err, "query")
154152
}
155153

156154
// From now on, we must only return with a finalizer in the result (to
@@ -189,7 +187,7 @@ func (q *QueryAPI) Wrap(f apiFunc) http.HandlerFunc {
189187
}
190188

191189
if result.err != nil {
192-
api.RespondFromGRPCError(q.logger, w, result.err.err)
190+
q.respondError(w, result.err, result.data)
193191
return
194192
}
195193

@@ -201,6 +199,47 @@ func (q *QueryAPI) Wrap(f apiFunc) http.HandlerFunc {
201199
}
202200
}
203201

202+
func (q *QueryAPI) respondError(w http.ResponseWriter, apiErr *apiError, data interface{}) {
203+
json := jsoniter.ConfigCompatibleWithStandardLibrary
204+
b, err := json.Marshal(&Response{
205+
Status: statusError,
206+
ErrorType: apiErr.typ,
207+
Error: apiErr.err.Error(),
208+
Data: data,
209+
})
210+
if err != nil {
211+
level.Error(q.logger).Log("error marshaling json response", "err", err)
212+
http.Error(w, err.Error(), http.StatusInternalServerError)
213+
return
214+
}
215+
216+
var code int
217+
switch apiErr.typ {
218+
case errorBadData:
219+
code = http.StatusBadRequest
220+
case errorExec:
221+
code = http.StatusUnprocessableEntity
222+
case errorCanceled:
223+
code = statusClientClosedConnection
224+
case errorTimeout:
225+
code = http.StatusServiceUnavailable
226+
case errorInternal:
227+
code = http.StatusInternalServerError
228+
case errorNotFound:
229+
code = http.StatusNotFound
230+
case errorNotAcceptable:
231+
code = http.StatusNotAcceptable
232+
default:
233+
code = http.StatusInternalServerError
234+
}
235+
236+
w.Header().Set("Content-Type", "application/json")
237+
w.WriteHeader(code)
238+
if n, err := w.Write(b); err != nil {
239+
level.Error(q.logger).Log("error writing response", "bytesWritten", n, "err", err)
240+
}
241+
}
242+
204243
func (q *QueryAPI) respond(w http.ResponseWriter, req *http.Request, data interface{}, warnings annotations.Annotations, query string) {
205244
warn, info := warnings.AsStrings(query, 10, 10)
206245

@@ -213,7 +252,7 @@ func (q *QueryAPI) respond(w http.ResponseWriter, req *http.Request, data interf
213252

214253
codec, err := q.negotiateCodec(req, resp)
215254
if err != nil {
216-
api.RespondFromGRPCError(q.logger, w, httpgrpc.Errorf(http.StatusNotAcceptable, "%s", &apiError{errorNotAcceptable, err}))
255+
q.respondError(w, &apiError{errorNotAcceptable, err}, nil)
217256
return
218257
}
219258

pkg/api/queryapi/query_api_test.go

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -94,25 +94,25 @@ func Test_CustomAPI(t *testing.T) {
9494
name: "[Range Query] empty start",
9595
path: "/api/v1/query_range?end=1536673680&query=test&step=5",
9696
expectedCode: http.StatusBadRequest,
97-
expectedBody: "{\"status\":\"error\",\"errorType\":\"bad_data\",\"error\":\"invalid parameter \\\"start\\\"; cannot parse \\\"\\\" to a valid timestamp\"}",
97+
expectedBody: "{\"status\":\"error\",\"errorType\":\"bad_data\",\"error\":\"invalid parameter \\\"start\\\": cannot parse \\\"\\\" to a valid timestamp\"}",
9898
},
9999
{
100100
name: "[Range Query] empty end",
101101
path: "/api/v1/query_range?query=test&start=1536673665&step=5",
102102
expectedCode: http.StatusBadRequest,
103-
expectedBody: "{\"status\":\"error\",\"errorType\":\"bad_data\",\"error\":\"invalid parameter \\\"end\\\"; cannot parse \\\"\\\" to a valid timestamp\"}",
103+
expectedBody: "{\"status\":\"error\",\"errorType\":\"bad_data\",\"error\":\"invalid parameter \\\"end\\\": cannot parse \\\"\\\" to a valid timestamp\"}",
104104
},
105105
{
106106
name: "[Range Query] start is greater than end",
107107
path: "/api/v1/query_range?end=1536673680&query=test&start=1536673681&step=5",
108108
expectedCode: http.StatusBadRequest,
109-
expectedBody: "{\"status\":\"error\",\"errorType\":\"bad_data\",\"error\":\"invalid parameter \\\"end\\\"; end timestamp must not be before start time\"}",
109+
expectedBody: "{\"status\":\"error\",\"errorType\":\"bad_data\",\"error\":\"invalid parameter \\\"end\\\": end timestamp must not be before start time\"}",
110110
},
111111
{
112112
name: "[Range Query] negative step",
113113
path: "/api/v1/query_range?end=1536673680&query=test&start=1536673665&step=-1",
114114
expectedCode: http.StatusBadRequest,
115-
expectedBody: "{\"status\":\"error\",\"errorType\":\"bad_data\",\"error\":\"invalid parameter \\\"step\\\"; zero or negative query resolution step widths are not accepted. Try a positive integer\"}",
115+
expectedBody: "{\"status\":\"error\",\"errorType\":\"bad_data\",\"error\":\"invalid parameter \\\"step\\\": zero or negative query resolution step widths are not accepted. Try a positive integer\"}",
116116
},
117117
{
118118
name: "[Range Query] returned points are over 11000",
@@ -124,19 +124,19 @@ func Test_CustomAPI(t *testing.T) {
124124
name: "[Range Query] empty query",
125125
path: "/api/v1/query_range?end=1536673680&start=1536673665&step=5",
126126
expectedCode: http.StatusBadRequest,
127-
expectedBody: "{\"status\":\"error\",\"errorType\":\"bad_data\",\"error\":\"invalid parameter \\\"query\\\"; unknown position: parse error: no expression found in input\"}",
127+
expectedBody: "{\"status\":\"error\",\"errorType\":\"bad_data\",\"error\":\"invalid parameter \\\"query\\\": unknown position: parse error: no expression found in input\"}",
128128
},
129129
{
130130
name: "[Range Query] invalid lookback delta",
131131
path: "/api/v1/query_range?end=1536673680&query=test&start=1536673665&step=5&lookback_delta=dummy",
132132
expectedCode: http.StatusBadRequest,
133-
expectedBody: "{\"status\":\"error\",\"errorType\":\"bad_data\",\"error\":\"error parsing lookback delta duration: rpc error: code = Code(400) desc = cannot parse \\\"dummy\\\" to a valid duration\"}",
133+
expectedBody: "{\"status\":\"error\",\"errorType\":\"bad_data\",\"error\":\"error parsing lookback delta duration: cannot parse \\\"dummy\\\" to a valid duration\"}",
134134
},
135135
{
136136
name: "[Range Query] invalid timeout delta",
137137
path: "/api/v1/query_range?end=1536673680&query=test&start=1536673665&step=5&timeout=dummy",
138138
expectedCode: http.StatusBadRequest,
139-
expectedBody: "{\"status\":\"error\",\"errorType\":\"bad_data\",\"error\":\"invalid parameter \\\"timeout\\\"; cannot parse \\\"dummy\\\" to a valid duration\"}",
139+
expectedBody: "{\"status\":\"error\",\"errorType\":\"bad_data\",\"error\":\"invalid parameter \\\"timeout\\\": cannot parse \\\"dummy\\\" to a valid duration\"}",
140140
},
141141
{
142142
name: "[Range Query] normal case",
@@ -148,19 +148,19 @@ func Test_CustomAPI(t *testing.T) {
148148
name: "[Instant Query] empty query",
149149
path: "/api/v1/query",
150150
expectedCode: http.StatusBadRequest,
151-
expectedBody: "{\"status\":\"error\",\"errorType\":\"bad_data\",\"error\":\"invalid parameter \\\"query\\\"; unknown position: parse error: no expression found in input\"}",
151+
expectedBody: "{\"status\":\"error\",\"errorType\":\"bad_data\",\"error\":\"invalid parameter \\\"query\\\": unknown position: parse error: no expression found in input\"}",
152152
},
153153
{
154154
name: "[Instant Query] invalid lookback delta",
155155
path: "/api/v1/query?lookback_delta=dummy",
156156
expectedCode: http.StatusBadRequest,
157-
expectedBody: "{\"status\":\"error\",\"errorType\":\"bad_data\",\"error\":\"error parsing lookback delta duration: rpc error: code = Code(400) desc = cannot parse \\\"dummy\\\" to a valid duration\"}",
157+
expectedBody: "{\"status\":\"error\",\"errorType\":\"bad_data\",\"error\":\"error parsing lookback delta duration: cannot parse \\\"dummy\\\" to a valid duration\"}",
158158
},
159159
{
160160
name: "[Instant Query] invalid timeout",
161161
path: "/api/v1/query?timeout=dummy",
162162
expectedCode: http.StatusBadRequest,
163-
expectedBody: "{\"status\":\"error\",\"errorType\":\"bad_data\",\"error\":\"invalid parameter \\\"timeout\\\"; cannot parse \\\"dummy\\\" to a valid duration\"}",
163+
expectedBody: "{\"status\":\"error\",\"errorType\":\"bad_data\",\"error\":\"invalid parameter \\\"timeout\\\": cannot parse \\\"dummy\\\" to a valid duration\"}",
164164
},
165165
{
166166
name: "[Instant Query] normal case",

pkg/api/queryapi/response.go

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package queryapi
2+
3+
import (
4+
"fmt"
5+
"net/http"
6+
7+
"github.com/gogo/status"
8+
"github.com/weaveworks/common/httpgrpc"
9+
)
10+
11+
var (
12+
ErrEndBeforeStart = httpgrpc.Errorf(http.StatusBadRequest, "%s", "end timestamp must not be before start time")
13+
ErrNegativeStep = httpgrpc.Errorf(http.StatusBadRequest, "%s", "zero or negative query resolution step widths are not accepted. Try a positive integer")
14+
ErrStepTooSmall = httpgrpc.Errorf(http.StatusBadRequest, "%s", "exceeded maximum resolution of 11,000 points per timeseries. Try decreasing the query resolution (?step=XX)")
15+
)
16+
17+
func DecorateWithParamName(err error, field string) error {
18+
errTmpl := "invalid parameter %q; %v"
19+
if status, ok := status.FromError(err); ok {
20+
return httpgrpc.Errorf(int(status.Code()), errTmpl, field, status.Message())
21+
}
22+
return fmt.Errorf(errTmpl, field, err)
23+
}
24+
25+
// Response defines the Prometheus response format.
26+
type Response struct {
27+
Status string `json:"status"`
28+
Data interface{} `json:"data,omitempty"`
29+
ErrorType errorType `json:"errorType,omitempty"`
30+
Error string `json:"error,omitempty"`
31+
Warnings []string `json:"warnings,omitempty"`
32+
Infos []string `json:"infos,omitempty"`
33+
}

0 commit comments

Comments
 (0)