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  }