github.com/thanos-io/thanos@v0.32.5/pkg/queryfrontend/labels_codec.go (about) 1 // Copyright (c) The Thanos Authors. 2 // Licensed under the Apache License 2.0. 3 4 package queryfrontend 5 6 import ( 7 "bytes" 8 "context" 9 "encoding/json" 10 io "io" 11 "math" 12 "net/http" 13 "net/url" 14 "sort" 15 "strconv" 16 "strings" 17 "time" 18 19 "github.com/opentracing/opentracing-go" 20 otlog "github.com/opentracing/opentracing-go/log" 21 "github.com/prometheus/prometheus/model/timestamp" 22 "github.com/weaveworks/common/httpgrpc" 23 24 "github.com/thanos-io/thanos/internal/cortex/querier/queryrange" 25 cortexutil "github.com/thanos-io/thanos/internal/cortex/util" 26 "github.com/thanos-io/thanos/internal/cortex/util/spanlogger" 27 queryv1 "github.com/thanos-io/thanos/pkg/api/query" 28 "github.com/thanos-io/thanos/pkg/store/labelpb" 29 ) 30 31 var ( 32 infMinTime = time.Unix(math.MinInt64/1000+62135596801, 0) 33 infMaxTime = time.Unix(math.MaxInt64/1000-62135596801, 999999999) 34 ) 35 36 // labelsCodec is used to encode/decode Thanos labels and series requests and responses. 37 type labelsCodec struct { 38 partialResponse bool 39 defaultMetadataTimeRange time.Duration 40 } 41 42 // NewThanosLabelsCodec initializes a labelsCodec. 43 func NewThanosLabelsCodec(partialResponse bool, defaultMetadataTimeRange time.Duration) *labelsCodec { 44 return &labelsCodec{ 45 partialResponse: partialResponse, 46 defaultMetadataTimeRange: defaultMetadataTimeRange, 47 } 48 } 49 50 // MergeResponse merges multiple responses into a single Response. It needs to dedup the responses and ensure the order. 51 func (c labelsCodec) MergeResponse(_ queryrange.Request, responses ...queryrange.Response) (queryrange.Response, error) { 52 if len(responses) == 0 { 53 // Empty response for label_names, label_values and series API. 54 return &ThanosLabelsResponse{ 55 Status: queryrange.StatusSuccess, 56 Data: []string{}, 57 }, nil 58 } 59 60 switch responses[0].(type) { 61 case *ThanosLabelsResponse: 62 if len(responses) == 1 { 63 return responses[0], nil 64 } 65 set := make(map[string]struct{}) 66 67 for _, res := range responses { 68 for _, value := range res.(*ThanosLabelsResponse).Data { 69 if _, ok := set[value]; !ok { 70 set[value] = struct{}{} 71 } 72 } 73 } 74 lbls := make([]string, 0, len(set)) 75 for label := range set { 76 lbls = append(lbls, label) 77 } 78 79 sort.Strings(lbls) 80 return &ThanosLabelsResponse{ 81 Status: queryrange.StatusSuccess, 82 Data: lbls, 83 }, nil 84 case *ThanosSeriesResponse: 85 seriesData := make(labelpb.ZLabelSets, 0) 86 87 uniqueSeries := make(map[string]struct{}) 88 for _, res := range responses { 89 for _, series := range res.(*ThanosSeriesResponse).Data { 90 s := series.PromLabels().String() 91 if _, ok := uniqueSeries[s]; !ok { 92 seriesData = append(seriesData, series) 93 uniqueSeries[s] = struct{}{} 94 } 95 } 96 } 97 98 sort.Sort(seriesData) 99 return &ThanosSeriesResponse{ 100 Status: queryrange.StatusSuccess, 101 Data: seriesData, 102 }, nil 103 default: 104 return nil, httpgrpc.Errorf(http.StatusInternalServerError, "invalid response format") 105 } 106 } 107 108 func (c labelsCodec) DecodeRequest(_ context.Context, r *http.Request, forwardHeaders []string) (queryrange.Request, error) { 109 if err := r.ParseForm(); err != nil { 110 return nil, httpgrpc.Errorf(http.StatusBadRequest, err.Error()) 111 } 112 113 var ( 114 req queryrange.Request 115 err error 116 ) 117 switch op := getOperation(r); op { 118 case labelNamesOp, labelValuesOp: 119 req, err = c.parseLabelsRequest(r, op, forwardHeaders) 120 case seriesOp: 121 req, err = c.parseSeriesRequest(r, forwardHeaders) 122 } 123 if err != nil { 124 return nil, err 125 } 126 127 return req, nil 128 } 129 130 func (c labelsCodec) EncodeRequest(ctx context.Context, r queryrange.Request) (*http.Request, error) { 131 var req *http.Request 132 var err error 133 switch thanosReq := r.(type) { 134 case *ThanosLabelsRequest: 135 var params = url.Values{ 136 "start": []string{encodeTime(thanosReq.Start)}, 137 "end": []string{encodeTime(thanosReq.End)}, 138 queryv1.PartialResponseParam: []string{strconv.FormatBool(thanosReq.PartialResponse)}, 139 } 140 if len(thanosReq.Matchers) > 0 { 141 params[queryv1.MatcherParam] = matchersToStringSlice(thanosReq.Matchers) 142 } 143 if len(thanosReq.StoreMatchers) > 0 { 144 params[queryv1.StoreMatcherParam] = matchersToStringSlice(thanosReq.StoreMatchers) 145 } 146 147 if strings.Contains(thanosReq.Path, "/api/v1/label/") { 148 u := &url.URL{ 149 Path: thanosReq.Path, 150 RawQuery: params.Encode(), 151 } 152 153 req = &http.Request{ 154 Method: http.MethodGet, 155 RequestURI: u.String(), // This is what the httpgrpc code looks at. 156 URL: u, 157 Body: http.NoBody, 158 Header: http.Header{}, 159 } 160 } else { 161 req, err = http.NewRequest(http.MethodPost, thanosReq.Path, bytes.NewBufferString(params.Encode())) 162 if err != nil { 163 return nil, httpgrpc.Errorf(http.StatusBadRequest, "error creating request: %s", err.Error()) 164 } 165 req.Header.Set("Content-Type", "application/x-www-form-urlencoded") 166 } 167 168 for _, hv := range thanosReq.Headers { 169 for _, v := range hv.Values { 170 req.Header.Add(hv.Name, v) 171 } 172 } 173 174 case *ThanosSeriesRequest: 175 var params = url.Values{ 176 "start": []string{encodeTime(thanosReq.Start)}, 177 "end": []string{encodeTime(thanosReq.End)}, 178 queryv1.DedupParam: []string{strconv.FormatBool(thanosReq.Dedup)}, 179 queryv1.PartialResponseParam: []string{strconv.FormatBool(thanosReq.PartialResponse)}, 180 queryv1.ReplicaLabelsParam: thanosReq.ReplicaLabels, 181 } 182 if len(thanosReq.Matchers) > 0 { 183 params[queryv1.MatcherParam] = matchersToStringSlice(thanosReq.Matchers) 184 } 185 if len(thanosReq.StoreMatchers) > 0 { 186 params[queryv1.StoreMatcherParam] = matchersToStringSlice(thanosReq.StoreMatchers) 187 } 188 189 req, err = http.NewRequest(http.MethodPost, thanosReq.Path, bytes.NewBufferString(params.Encode())) 190 if err != nil { 191 return nil, httpgrpc.Errorf(http.StatusBadRequest, "error creating request: %s", err.Error()) 192 } 193 req.Header.Set("Content-Type", "application/x-www-form-urlencoded") 194 for _, hv := range thanosReq.Headers { 195 for _, v := range hv.Values { 196 req.Header.Add(hv.Name, v) 197 } 198 } 199 200 default: 201 return nil, httpgrpc.Errorf(http.StatusInternalServerError, "invalid request format") 202 } 203 204 return req.WithContext(ctx), nil 205 } 206 207 func (c labelsCodec) DecodeResponse(ctx context.Context, r *http.Response, req queryrange.Request) (queryrange.Response, error) { 208 if r.StatusCode/100 != 2 { 209 body, _ := io.ReadAll(r.Body) 210 return nil, httpgrpc.Errorf(r.StatusCode, string(body)) 211 } 212 log, _ := spanlogger.New(ctx, "ParseQueryResponse") //nolint:ineffassign,staticcheck 213 defer log.Finish() 214 215 buf, err := io.ReadAll(r.Body) 216 if err != nil { 217 log.Error(err) //nolint:errcheck 218 return nil, httpgrpc.Errorf(http.StatusInternalServerError, "error decoding response: %v", err) 219 } 220 221 log.LogFields(otlog.Int("bytes", len(buf))) 222 223 switch req.(type) { 224 case *ThanosLabelsRequest: 225 var resp ThanosLabelsResponse 226 if err := json.Unmarshal(buf, &resp); err != nil { 227 return nil, httpgrpc.Errorf(http.StatusInternalServerError, "error decoding response: %v", err) 228 } 229 for h, hv := range r.Header { 230 resp.Headers = append(resp.Headers, &ResponseHeader{Name: h, Values: hv}) 231 } 232 return &resp, nil 233 case *ThanosSeriesRequest: 234 var resp ThanosSeriesResponse 235 if err := json.Unmarshal(buf, &resp); err != nil { 236 return nil, httpgrpc.Errorf(http.StatusInternalServerError, "error decoding response: %v", err) 237 } 238 for h, hv := range r.Header { 239 resp.Headers = append(resp.Headers, &ResponseHeader{Name: h, Values: hv}) 240 } 241 return &resp, nil 242 default: 243 return nil, httpgrpc.Errorf(http.StatusInternalServerError, "invalid request type") 244 } 245 } 246 247 func (c labelsCodec) EncodeResponse(ctx context.Context, res queryrange.Response) (*http.Response, error) { 248 sp, _ := opentracing.StartSpanFromContext(ctx, "APIResponse.ToHTTPResponse") 249 defer sp.Finish() 250 251 var ( 252 b []byte 253 err error 254 ) 255 switch resp := res.(type) { 256 case *ThanosLabelsResponse: 257 sp.LogFields(otlog.Int("labels", len(resp.Data))) 258 b, err = json.Marshal(resp) 259 if err != nil { 260 return nil, httpgrpc.Errorf(http.StatusInternalServerError, "error encoding response: %v", err) 261 } 262 case *ThanosSeriesResponse: 263 sp.LogFields(otlog.Int("series", len(resp.Data))) 264 b, err = json.Marshal(resp) 265 if err != nil { 266 return nil, httpgrpc.Errorf(http.StatusInternalServerError, "error encoding response: %v", err) 267 } 268 default: 269 return nil, httpgrpc.Errorf(http.StatusInternalServerError, "invalid response format") 270 } 271 272 sp.LogFields(otlog.Int("bytes", len(b))) 273 resp := http.Response{ 274 Header: http.Header{ 275 "Content-Type": []string{"application/json"}, 276 }, 277 Body: io.NopCloser(bytes.NewBuffer(b)), 278 StatusCode: http.StatusOK, 279 } 280 return &resp, nil 281 } 282 283 func (c labelsCodec) parseLabelsRequest(r *http.Request, op string, forwardHeaders []string) (queryrange.Request, error) { 284 var ( 285 result ThanosLabelsRequest 286 err error 287 ) 288 result.Start, result.End, err = parseMetadataTimeRange(r, c.defaultMetadataTimeRange) 289 if err != nil { 290 return nil, err 291 } 292 293 result.Matchers, err = parseMatchersParam(r.Form, queryv1.MatcherParam) 294 if err != nil { 295 return nil, err 296 } 297 298 result.PartialResponse, err = parsePartialResponseParam(r.FormValue(queryv1.PartialResponseParam), c.partialResponse) 299 if err != nil { 300 return nil, err 301 } 302 303 result.StoreMatchers, err = parseMatchersParam(r.Form, queryv1.StoreMatcherParam) 304 if err != nil { 305 return nil, err 306 } 307 308 result.Path = r.URL.Path 309 310 if op == labelValuesOp { 311 parts := strings.Split(r.URL.Path, "/") 312 if len(parts) > 1 { 313 result.Label = parts[len(parts)-2] 314 } 315 } 316 317 for _, value := range r.Header.Values(cacheControlHeader) { 318 if strings.Contains(value, noStoreValue) { 319 result.CachingOptions.Disabled = true 320 break 321 } 322 } 323 324 // Include the specified headers from http request in prometheusRequest. 325 for _, header := range forwardHeaders { 326 for h, hv := range r.Header { 327 if strings.EqualFold(h, header) { 328 result.Headers = append(result.Headers, &RequestHeader{Name: h, Values: hv}) 329 break 330 } 331 } 332 } 333 334 return &result, nil 335 } 336 337 func (c labelsCodec) parseSeriesRequest(r *http.Request, forwardHeaders []string) (queryrange.Request, error) { 338 var ( 339 result ThanosSeriesRequest 340 err error 341 ) 342 result.Start, result.End, err = parseMetadataTimeRange(r, c.defaultMetadataTimeRange) 343 if err != nil { 344 return nil, err 345 } 346 347 result.Matchers, err = parseMatchersParam(r.Form, queryv1.MatcherParam) 348 if err != nil { 349 return nil, err 350 } 351 352 result.Dedup, err = parseEnableDedupParam(r.FormValue(queryv1.DedupParam)) 353 if err != nil { 354 return nil, err 355 } 356 357 result.PartialResponse, err = parsePartialResponseParam(r.FormValue(queryv1.PartialResponseParam), c.partialResponse) 358 if err != nil { 359 return nil, err 360 } 361 362 if len(r.Form[queryv1.ReplicaLabelsParam]) > 0 { 363 result.ReplicaLabels = r.Form[queryv1.ReplicaLabelsParam] 364 } 365 366 result.StoreMatchers, err = parseMatchersParam(r.Form, queryv1.StoreMatcherParam) 367 if err != nil { 368 return nil, err 369 } 370 371 result.Path = r.URL.Path 372 373 for _, value := range r.Header.Values(cacheControlHeader) { 374 if strings.Contains(value, noStoreValue) { 375 result.CachingOptions.Disabled = true 376 break 377 } 378 } 379 380 // Include the specified headers from http request in prometheusRequest. 381 for _, header := range forwardHeaders { 382 for h, hv := range r.Header { 383 if strings.EqualFold(h, header) { 384 result.Headers = append(result.Headers, &RequestHeader{Name: h, Values: hv}) 385 break 386 } 387 } 388 } 389 390 return &result, nil 391 } 392 393 func parseMetadataTimeRange(r *http.Request, defaultMetadataTimeRange time.Duration) (int64, int64, error) { 394 // If start and end time not specified as query parameter, we get the range from the beginning of time by default. 395 var defaultStartTime, defaultEndTime time.Time 396 if defaultMetadataTimeRange == 0 { 397 defaultStartTime = infMinTime 398 defaultEndTime = infMaxTime 399 } else { 400 now := time.Now() 401 defaultStartTime = now.Add(-defaultMetadataTimeRange) 402 defaultEndTime = now 403 } 404 405 start, err := parseTimeParam(r, "start", defaultStartTime) 406 if err != nil { 407 return 0, 0, err 408 } 409 end, err := parseTimeParam(r, "end", defaultEndTime) 410 if err != nil { 411 return 0, 0, err 412 } 413 if end < start { 414 return 0, 0, errEndBeforeStart 415 } 416 417 return start, end, nil 418 } 419 420 func parseTimeParam(r *http.Request, paramName string, defaultValue time.Time) (int64, error) { 421 val := r.FormValue(paramName) 422 if val == "" { 423 return timestamp.FromTime(defaultValue), nil 424 } 425 result, err := cortexutil.ParseTime(val) 426 if err != nil { 427 return 0, err 428 } 429 return result, nil 430 }