github.com/thanos-io/thanos@v0.32.5/pkg/api/query/v1_test.go (about)

     1  // Copyright (c) The Thanos Authors.
     2  // Licensed under the Apache License 2.0.
     3  
     4  // Copyright 2016 The Prometheus Authors
     5  // Licensed under the Apache License, Version 2.0 (the "License");
     6  // you may not use this file except in compliance with the License.
     7  // You may obtain a copy of the License at
     8  //
     9  //     http://www.apache.org/licenses/LICENSE-2.0
    10  //
    11  // Unless required by applicable law or agreed to in writing, software
    12  // distributed under the License is distributed on an "AS IS" BASIS,
    13  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    14  // See the License for the specific language governing permissions and
    15  // limitations under the License.
    16  
    17  package v1
    18  
    19  import (
    20  	"context"
    21  	"encoding/json"
    22  	"fmt"
    23  	"io"
    24  	"math"
    25  	"math/rand"
    26  	"net/http"
    27  	"net/url"
    28  	"reflect"
    29  	"strings"
    30  	"testing"
    31  	"time"
    32  
    33  	"github.com/efficientgo/core/testutil"
    34  	"github.com/go-kit/log"
    35  	"github.com/prometheus/client_golang/prometheus"
    36  	"github.com/prometheus/client_golang/prometheus/promauto"
    37  	"github.com/prometheus/common/route"
    38  	"github.com/prometheus/prometheus/model/histogram"
    39  	"github.com/prometheus/prometheus/model/labels"
    40  	"github.com/prometheus/prometheus/model/timestamp"
    41  	"github.com/prometheus/prometheus/promql"
    42  	"github.com/prometheus/prometheus/promql/parser"
    43  	"github.com/prometheus/prometheus/rules"
    44  	"github.com/prometheus/prometheus/storage"
    45  	"github.com/prometheus/prometheus/tsdb"
    46  	"github.com/prometheus/prometheus/tsdb/chunkenc"
    47  	"github.com/prometheus/prometheus/tsdb/tsdbutil"
    48  	promgate "github.com/prometheus/prometheus/util/gate"
    49  	"github.com/prometheus/prometheus/util/stats"
    50  	baseAPI "github.com/thanos-io/thanos/pkg/api"
    51  	"github.com/thanos-io/thanos/pkg/compact"
    52  	"github.com/thanos-io/thanos/pkg/component"
    53  	"github.com/thanos-io/thanos/pkg/gate"
    54  	"github.com/thanos-io/thanos/pkg/query"
    55  	"github.com/thanos-io/thanos/pkg/rules/rulespb"
    56  	"github.com/thanos-io/thanos/pkg/store"
    57  	"github.com/thanos-io/thanos/pkg/store/labelpb"
    58  	"github.com/thanos-io/thanos/pkg/store/storepb"
    59  	storetestutil "github.com/thanos-io/thanos/pkg/store/storepb/testutil"
    60  	"github.com/thanos-io/thanos/pkg/testutil/custom"
    61  	"github.com/thanos-io/thanos/pkg/testutil/e2eutil"
    62  	"github.com/thanos-io/thanos/pkg/testutil/testpromcompatibility"
    63  )
    64  
    65  func TestMain(m *testing.M) {
    66  	custom.TolerantVerifyLeakMain(m)
    67  }
    68  
    69  type endpointTestCase struct {
    70  	endpoint baseAPI.ApiFunc
    71  	params   map[string]string
    72  	query    url.Values
    73  	method   string
    74  	response interface{}
    75  	errType  baseAPI.ErrorType
    76  }
    77  type responeCompareFunction func(interface{}, interface{}) bool
    78  
    79  // Checks if both responses have Stats present or not.
    80  func lookupStats(a, b interface{}) bool {
    81  	ra := a.(*queryData)
    82  	rb := b.(*queryData)
    83  	return (ra.Stats == nil && rb.Stats == nil) || (ra.Stats != nil && rb.Stats != nil)
    84  }
    85  
    86  func testEndpoint(t *testing.T, test endpointTestCase, name string, responseCompareFunc responeCompareFunction) bool {
    87  	return t.Run(name, func(t *testing.T) {
    88  		// Build a context with the correct request params.
    89  		ctx := context.Background()
    90  		for p, v := range test.params {
    91  			ctx = route.WithParam(ctx, p, v)
    92  		}
    93  
    94  		reqURL := "http://example.com"
    95  		params := test.query.Encode()
    96  
    97  		var body io.Reader
    98  		if test.method == http.MethodPost {
    99  			body = strings.NewReader(params)
   100  		} else if test.method == "" {
   101  			test.method = "ANY"
   102  			reqURL += "?" + params
   103  		}
   104  
   105  		req, err := http.NewRequest(test.method, reqURL, body)
   106  		if err != nil {
   107  			t.Fatal(err)
   108  		}
   109  
   110  		if body != nil {
   111  			req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
   112  		}
   113  
   114  		resp, _, apiErr, releaseResources := test.endpoint(req.WithContext(ctx))
   115  		defer releaseResources()
   116  		if apiErr != nil {
   117  			if test.errType == baseAPI.ErrorNone {
   118  				t.Fatalf("Unexpected error: %s", apiErr)
   119  			}
   120  			if test.errType != apiErr.Typ {
   121  				t.Fatalf("Expected error of type %q but got type %q", test.errType, apiErr.Typ)
   122  			}
   123  			return
   124  		}
   125  		if test.errType != baseAPI.ErrorNone {
   126  			t.Fatalf("Expected error of type %q but got none", test.errType)
   127  		}
   128  
   129  		if !responseCompareFunc(resp, test.response) {
   130  			t.Fatalf("Response does not match, expected:\n%+v\ngot:\n%+v", test.response, resp)
   131  		}
   132  	})
   133  }
   134  
   135  func TestQueryEndpoints(t *testing.T) {
   136  	lbls := []labels.Labels{
   137  		{
   138  			labels.Label{Name: "__name__", Value: "test_metric1"},
   139  			labels.Label{Name: "foo", Value: "bar"},
   140  		},
   141  		{
   142  			labels.Label{Name: "__name__", Value: "test_metric1"},
   143  			labels.Label{Name: "foo", Value: "boo"},
   144  		},
   145  		{
   146  			labels.Label{Name: "__name__", Value: "test_metric2"},
   147  			labels.Label{Name: "foo", Value: "boo"},
   148  		},
   149  		{
   150  			labels.Label{Name: "__name__", Value: "test_metric_replica1"},
   151  			labels.Label{Name: "foo", Value: "bar"},
   152  			labels.Label{Name: "replica", Value: "a"},
   153  		},
   154  		{
   155  			labels.Label{Name: "__name__", Value: "test_metric_replica1"},
   156  			labels.Label{Name: "foo", Value: "boo"},
   157  			labels.Label{Name: "replica", Value: "a"},
   158  		},
   159  		{
   160  			labels.Label{Name: "__name__", Value: "test_metric_replica1"},
   161  			labels.Label{Name: "foo", Value: "boo"},
   162  			labels.Label{Name: "replica", Value: "b"},
   163  		},
   164  		{
   165  			labels.Label{Name: "__name__", Value: "test_metric_replica1"},
   166  			labels.Label{Name: "foo", Value: "boo"},
   167  			labels.Label{Name: "replica1", Value: "a"},
   168  		},
   169  	}
   170  
   171  	db, err := e2eutil.NewTSDB()
   172  	defer func() { testutil.Ok(t, db.Close()) }()
   173  	testutil.Ok(t, err)
   174  
   175  	app := db.Appender(context.Background())
   176  	for _, lbl := range lbls {
   177  		for i := int64(0); i < 10; i++ {
   178  			_, err := app.Append(0, lbl, i*60000, float64(i))
   179  			testutil.Ok(t, err)
   180  		}
   181  	}
   182  	testutil.Ok(t, app.Commit())
   183  
   184  	now := time.Now()
   185  	timeout := 100 * time.Second
   186  	ef := NewQueryEngineFactory(promql.EngineOpts{
   187  		Logger:     nil,
   188  		Reg:        nil,
   189  		MaxSamples: 10000,
   190  		Timeout:    timeout,
   191  	}, nil)
   192  	api := &QueryAPI{
   193  		baseAPI: &baseAPI.BaseAPI{
   194  			Now: func() time.Time { return now },
   195  		},
   196  		queryableCreate:       query.NewQueryableCreator(nil, nil, newProxyStoreWithTSDBStore(db), 2, timeout),
   197  		engineFactory:         ef,
   198  		defaultEngine:         PromqlEnginePrometheus,
   199  		lookbackDeltaCreate:   func(m int64) time.Duration { return time.Duration(0) },
   200  		gate:                  gate.New(nil, 4, gate.Queries),
   201  		defaultRangeQueryStep: time.Second,
   202  		queryRangeHist: promauto.With(prometheus.NewRegistry()).NewHistogram(prometheus.HistogramOpts{
   203  			Name: "query_range_hist",
   204  		}),
   205  		seriesStatsAggregatorFactory: &store.NoopSeriesStatsAggregatorFactory{},
   206  		tenantHeader:                 "thanos-tenant",
   207  		defaultTenant:                "default-tenant",
   208  	}
   209  
   210  	start := time.Unix(0, 0)
   211  
   212  	var tests = []endpointTestCase{
   213  		{
   214  			endpoint: api.query,
   215  			query: url.Values{
   216  				"query": []string{"2"},
   217  				"time":  []string{"123.4"},
   218  			},
   219  			response: &queryData{
   220  				ResultType: parser.ValueTypeScalar,
   221  				Result: promql.Scalar{
   222  					V: 2,
   223  					T: timestamp.FromTime(start.Add(123*time.Second + 400*time.Millisecond)),
   224  				},
   225  			},
   226  		},
   227  		{
   228  			endpoint: api.query,
   229  			query: url.Values{
   230  				"query": []string{"0.333"},
   231  				"time":  []string{"1970-01-01T00:02:03Z"},
   232  			},
   233  			response: &queryData{
   234  				ResultType: parser.ValueTypeScalar,
   235  				Result: promql.Scalar{
   236  					V: 0.333,
   237  					T: timestamp.FromTime(start.Add(123 * time.Second)),
   238  				},
   239  			},
   240  		},
   241  		{
   242  			endpoint: api.query,
   243  			query: url.Values{
   244  				"query": []string{"0.333"},
   245  				"time":  []string{"1970-01-01T01:02:03+01:00"},
   246  			},
   247  			response: &queryData{
   248  				ResultType: parser.ValueTypeScalar,
   249  				Result: promql.Scalar{
   250  					V: 0.333,
   251  					T: timestamp.FromTime(start.Add(123 * time.Second)),
   252  				},
   253  			},
   254  		},
   255  		// Query endpoint without deduplication.
   256  		{
   257  			endpoint: api.query,
   258  			query: url.Values{
   259  				"query": []string{"test_metric_replica1"},
   260  				"time":  []string{"1970-01-01T01:02:03+01:00"},
   261  			},
   262  			response: &queryData{
   263  				ResultType: parser.ValueTypeVector,
   264  				Result: promql.Vector{
   265  					{
   266  						Metric: labels.Labels{
   267  							{
   268  								Name:  "__name__",
   269  								Value: "test_metric_replica1",
   270  							},
   271  							{
   272  								Name:  "foo",
   273  								Value: "bar",
   274  							},
   275  							{
   276  								Name:  "replica",
   277  								Value: "a",
   278  							},
   279  						},
   280  						T: 123000,
   281  						F: 2,
   282  					},
   283  					{
   284  						Metric: labels.Labels{
   285  							{
   286  								Name:  "__name__",
   287  								Value: "test_metric_replica1",
   288  							},
   289  							{
   290  								Name:  "foo",
   291  								Value: "boo",
   292  							},
   293  							{
   294  								Name:  "replica",
   295  								Value: "a",
   296  							},
   297  						},
   298  						T: 123000,
   299  						F: 2,
   300  					},
   301  					{
   302  						Metric: labels.Labels{
   303  							{
   304  								Name:  "__name__",
   305  								Value: "test_metric_replica1",
   306  							},
   307  							{
   308  								Name:  "foo",
   309  								Value: "boo",
   310  							},
   311  							{
   312  								Name:  "replica",
   313  								Value: "b",
   314  							},
   315  						},
   316  						T: 123000,
   317  						F: 2,
   318  					},
   319  					{
   320  						Metric: labels.Labels{
   321  							{
   322  								Name:  "__name__",
   323  								Value: "test_metric_replica1",
   324  							},
   325  							{
   326  								Name:  "foo",
   327  								Value: "boo",
   328  							},
   329  							{
   330  								Name:  "replica1",
   331  								Value: "a",
   332  							},
   333  						},
   334  						T: 123000,
   335  						F: 2,
   336  					},
   337  				},
   338  			},
   339  		},
   340  		// Query endpoint with single deduplication label.
   341  		{
   342  			endpoint: api.query,
   343  			query: url.Values{
   344  				"query":           []string{"test_metric_replica1"},
   345  				"time":            []string{"1970-01-01T01:02:03+01:00"},
   346  				"replicaLabels[]": []string{"replica"},
   347  			},
   348  			response: &queryData{
   349  				ResultType: parser.ValueTypeVector,
   350  				Result: promql.Vector{
   351  					{
   352  						Metric: labels.Labels{
   353  							{
   354  								Name:  "__name__",
   355  								Value: "test_metric_replica1",
   356  							},
   357  							{
   358  								Name:  "foo",
   359  								Value: "bar",
   360  							},
   361  						},
   362  						T: 123000,
   363  						F: 2,
   364  					},
   365  					{
   366  						Metric: labels.Labels{
   367  							{
   368  								Name:  "__name__",
   369  								Value: "test_metric_replica1",
   370  							},
   371  							{
   372  								Name:  "foo",
   373  								Value: "boo",
   374  							},
   375  						},
   376  						T: 123000,
   377  						F: 2,
   378  					},
   379  					{
   380  						Metric: labels.Labels{
   381  							{
   382  								Name:  "__name__",
   383  								Value: "test_metric_replica1",
   384  							},
   385  							{
   386  								Name:  "foo",
   387  								Value: "boo",
   388  							},
   389  							{
   390  								Name:  "replica1",
   391  								Value: "a",
   392  							},
   393  						},
   394  						T: 123000,
   395  						F: 2,
   396  					},
   397  				},
   398  			},
   399  		},
   400  		// Query endpoint with multiple deduplication label.
   401  		{
   402  			endpoint: api.query,
   403  			query: url.Values{
   404  				"query":           []string{"test_metric_replica1"},
   405  				"time":            []string{"1970-01-01T01:02:03+01:00"},
   406  				"replicaLabels[]": []string{"replica", "replica1"},
   407  			},
   408  			response: &queryData{
   409  				ResultType: parser.ValueTypeVector,
   410  				Result: promql.Vector{
   411  					{
   412  						Metric: labels.Labels{
   413  							{
   414  								Name:  "__name__",
   415  								Value: "test_metric_replica1",
   416  							},
   417  							{
   418  								Name:  "foo",
   419  								Value: "bar",
   420  							},
   421  						},
   422  						T: 123000,
   423  						F: 2,
   424  					},
   425  					{
   426  						Metric: labels.Labels{
   427  							{
   428  								Name:  "__name__",
   429  								Value: "test_metric_replica1",
   430  							},
   431  							{
   432  								Name:  "foo",
   433  								Value: "boo",
   434  							},
   435  						},
   436  						T: 123000,
   437  						F: 2,
   438  					},
   439  				},
   440  			},
   441  		},
   442  		{
   443  			endpoint: api.query,
   444  			query: url.Values{
   445  				"query": []string{"0.333"},
   446  			},
   447  			response: &queryData{
   448  				ResultType: parser.ValueTypeScalar,
   449  				Result: promql.Scalar{
   450  					V: 0.333,
   451  					T: timestamp.FromTime(now),
   452  				},
   453  			},
   454  		},
   455  		// Bad dedup parameter.
   456  		{
   457  			endpoint: api.query,
   458  			query: url.Values{
   459  				"query": []string{"0.333"},
   460  				"dedup": []string{"sdfsf"},
   461  			},
   462  			errType: baseAPI.ErrorBadData,
   463  		},
   464  		{
   465  			endpoint: api.queryRange,
   466  			query: url.Values{
   467  				"query": []string{"time()"},
   468  				"start": []string{"0"},
   469  				"end":   []string{"500"},
   470  				"step":  []string{"1"},
   471  			},
   472  			response: &queryData{
   473  				ResultType: parser.ValueTypeMatrix,
   474  				Result: promql.Matrix{
   475  					promql.Series{
   476  						Floats: func(end, step float64) []promql.FPoint {
   477  							var res []promql.FPoint
   478  							for v := float64(0); v <= end; v += step {
   479  								res = append(res, promql.FPoint{F: v, T: timestamp.FromTime(start.Add(time.Duration(v) * time.Second))})
   480  							}
   481  							return res
   482  						}(500, 1),
   483  						Metric: nil,
   484  					},
   485  				},
   486  			},
   487  		},
   488  		// Use default step when missing.
   489  		{
   490  			endpoint: api.queryRange,
   491  			query: url.Values{
   492  				"query": []string{"time()"},
   493  				"start": []string{"0"},
   494  				"end":   []string{"2"},
   495  			},
   496  			response: &queryData{
   497  				ResultType: parser.ValueTypeMatrix,
   498  				Result: promql.Matrix{
   499  					promql.Series{
   500  						Floats: []promql.FPoint{
   501  							{F: 0, T: timestamp.FromTime(start)},
   502  							{F: 1, T: timestamp.FromTime(start.Add(1 * time.Second))},
   503  							{F: 2, T: timestamp.FromTime(start.Add(2 * time.Second))},
   504  						},
   505  						Metric: nil,
   506  					},
   507  				},
   508  			},
   509  		},
   510  		// Missing query params in range queries.
   511  		{
   512  			endpoint: api.queryRange,
   513  			query: url.Values{
   514  				"query": []string{"time()"},
   515  				"end":   []string{"2"},
   516  				"step":  []string{"1"},
   517  			},
   518  			errType: baseAPI.ErrorBadData,
   519  		},
   520  		{
   521  			endpoint: api.queryRange,
   522  			query: url.Values{
   523  				"query": []string{"time()"},
   524  				"start": []string{"0"},
   525  				"step":  []string{"1"},
   526  			},
   527  			errType: baseAPI.ErrorBadData,
   528  		},
   529  		// Bad query expression.
   530  		{
   531  			endpoint: api.query,
   532  			query: url.Values{
   533  				"query": []string{"invalid][query"},
   534  				"time":  []string{"1970-01-01T01:02:03+01:00"},
   535  			},
   536  			errType: baseAPI.ErrorBadData,
   537  		},
   538  		{
   539  			endpoint: api.queryRange,
   540  			query: url.Values{
   541  				"query": []string{"invalid][query"},
   542  				"start": []string{"0"},
   543  				"end":   []string{"100"},
   544  				"step":  []string{"1"},
   545  			},
   546  			errType: baseAPI.ErrorBadData,
   547  		},
   548  		// Invalid step.
   549  		{
   550  			endpoint: api.queryRange,
   551  			query: url.Values{
   552  				"query": []string{"time()"},
   553  				"start": []string{"1"},
   554  				"end":   []string{"2"},
   555  				"step":  []string{"0"},
   556  			},
   557  			errType: baseAPI.ErrorBadData,
   558  		},
   559  		// Start after end.
   560  		{
   561  			endpoint: api.queryRange,
   562  			query: url.Values{
   563  				"query": []string{"time()"},
   564  				"start": []string{"2"},
   565  				"end":   []string{"1"},
   566  				"step":  []string{"1"},
   567  			},
   568  			errType: baseAPI.ErrorBadData,
   569  		},
   570  		// Start overflows int64 internally.
   571  		{
   572  			endpoint: api.queryRange,
   573  			query: url.Values{
   574  				"query": []string{"time()"},
   575  				"start": []string{"148966367200.372"},
   576  				"end":   []string{"1489667272.372"},
   577  				"step":  []string{"1"},
   578  			},
   579  			errType: baseAPI.ErrorBadData,
   580  		},
   581  		// Bad dedup parameter.
   582  		{
   583  			endpoint: api.queryRange,
   584  			query: url.Values{
   585  				"query": []string{"time()"},
   586  				"start": []string{"0"},
   587  				"end":   []string{"2"},
   588  				"step":  []string{"1"},
   589  				"dedup": []string{"sdfsf-range"},
   590  			},
   591  			errType: baseAPI.ErrorBadData,
   592  		},
   593  	}
   594  
   595  	for i, test := range tests {
   596  		if ok := testEndpoint(t, test, fmt.Sprintf("#%d %s", i, test.query.Encode()), reflect.DeepEqual); !ok {
   597  			return
   598  		}
   599  	}
   600  
   601  	qs := &stats.BuiltinStats{}
   602  	tests = []endpointTestCase{
   603  		{
   604  			endpoint: api.query,
   605  			query: url.Values{
   606  				"query": []string{"2"},
   607  				"time":  []string{"123.4"},
   608  			},
   609  			response: &queryData{},
   610  		},
   611  		{
   612  			endpoint: api.query,
   613  			query: url.Values{
   614  				"query": []string{"2"},
   615  				"time":  []string{"123.4"},
   616  				"stats": []string{"true"},
   617  			},
   618  			response: &queryData{
   619  				Stats: qs,
   620  			},
   621  		},
   622  	}
   623  
   624  	for i, test := range tests {
   625  		if ok := testEndpoint(t, test, fmt.Sprintf("#%d %s", i, test.query.Encode()), lookupStats); !ok {
   626  			return
   627  		}
   628  	}
   629  }
   630  
   631  func newProxyStoreWithTSDBStore(db store.TSDBReader) *store.ProxyStore {
   632  	c := &storetestutil.TestClient{
   633  		Name:        "1",
   634  		StoreClient: storepb.ServerAsClient(store.NewTSDBStore(nil, db, component.Query, nil), 0),
   635  		MinTime:     math.MinInt64, MaxTime: math.MaxInt64,
   636  	}
   637  
   638  	return store.NewProxyStore(
   639  		nil,
   640  		nil,
   641  		func() []store.Client { return []store.Client{c} },
   642  		component.Query,
   643  		nil,
   644  		0,
   645  		store.EagerRetrieval,
   646  	)
   647  }
   648  
   649  func TestMetadataEndpoints(t *testing.T) {
   650  	var old = []labels.Labels{
   651  		{
   652  			labels.Label{Name: "__name__", Value: "test_metric1"},
   653  			labels.Label{Name: "foo", Value: "bar"},
   654  		},
   655  		{
   656  			labels.Label{Name: "__name__", Value: "test_metric1"},
   657  			labels.Label{Name: "foo", Value: "boo"},
   658  		},
   659  		{
   660  			labels.Label{Name: "__name__", Value: "test_metric2"},
   661  			labels.Label{Name: "foo", Value: "boo"},
   662  		},
   663  	}
   664  
   665  	var recent = []labels.Labels{
   666  		{
   667  			labels.Label{Name: "__name__", Value: "test_metric_replica1"},
   668  			labels.Label{Name: "foo", Value: "bar"},
   669  			labels.Label{Name: "replica", Value: "a"},
   670  		},
   671  		{
   672  			labels.Label{Name: "__name__", Value: "test_metric_replica1"},
   673  			labels.Label{Name: "foo", Value: "boo"},
   674  			labels.Label{Name: "replica", Value: "a"},
   675  		},
   676  		{
   677  			labels.Label{Name: "__name__", Value: "test_metric_replica1"},
   678  			labels.Label{Name: "foo", Value: "boo"},
   679  			labels.Label{Name: "replica", Value: "b"},
   680  		},
   681  		{
   682  			labels.Label{Name: "__name__", Value: "test_metric_replica2"},
   683  			labels.Label{Name: "foo", Value: "boo"},
   684  			labels.Label{Name: "replica1", Value: "a"},
   685  		},
   686  	}
   687  
   688  	dir := t.TempDir()
   689  
   690  	const chunkRange int64 = 600_000
   691  	var series []storage.Series
   692  
   693  	for _, lbl := range old {
   694  		var samples []tsdbutil.Sample
   695  
   696  		for i := int64(0); i < 10; i++ {
   697  			samples = append(samples, sample{
   698  				t: i * 60_000,
   699  				f: float64(i),
   700  			})
   701  		}
   702  
   703  		series = append(series, storage.NewListSeries(lbl, samples))
   704  	}
   705  
   706  	_, err := tsdb.CreateBlock(series, dir, chunkRange, log.NewNopLogger())
   707  	testutil.Ok(t, err)
   708  
   709  	opts := tsdb.DefaultOptions()
   710  	opts.RetentionDuration = math.MaxInt64
   711  	db, err := tsdb.Open(dir, nil, nil, opts, nil)
   712  	defer func() { testutil.Ok(t, db.Close()) }()
   713  	testutil.Ok(t, err)
   714  
   715  	var (
   716  		apiLookbackDelta = 2 * time.Hour
   717  		start            = time.Now().Add(-apiLookbackDelta).Unix() * 1000
   718  		app              = db.Appender(context.Background())
   719  	)
   720  	for _, lbl := range recent {
   721  		for i := int64(0); i < 10; i++ {
   722  			_, err := app.Append(0, lbl, start+(i*60_000), float64(i)) // ms
   723  			testutil.Ok(t, err)
   724  		}
   725  	}
   726  	testutil.Ok(t, app.Commit())
   727  
   728  	now := time.Now()
   729  	timeout := 100 * time.Second
   730  	ef := NewQueryEngineFactory(promql.EngineOpts{
   731  		Logger:     nil,
   732  		Reg:        nil,
   733  		MaxSamples: 10000,
   734  		Timeout:    timeout,
   735  	}, nil)
   736  	api := &QueryAPI{
   737  		baseAPI: &baseAPI.BaseAPI{
   738  			Now: func() time.Time { return now },
   739  		},
   740  		queryableCreate:     query.NewQueryableCreator(nil, nil, newProxyStoreWithTSDBStore(db), 2, timeout),
   741  		engineFactory:       ef,
   742  		defaultEngine:       PromqlEnginePrometheus,
   743  		lookbackDeltaCreate: func(m int64) time.Duration { return time.Duration(0) },
   744  		gate:                gate.New(nil, 4, gate.Queries),
   745  		queryRangeHist: promauto.With(prometheus.NewRegistry()).NewHistogram(prometheus.HistogramOpts{
   746  			Name: "query_range_hist",
   747  		}),
   748  		seriesStatsAggregatorFactory: &store.NoopSeriesStatsAggregatorFactory{},
   749  		tenantHeader:                 "thanos-tenant",
   750  		defaultTenant:                "default-tenant",
   751  	}
   752  	apiWithLabelLookback := &QueryAPI{
   753  		baseAPI: &baseAPI.BaseAPI{
   754  			Now: func() time.Time { return now },
   755  		},
   756  		queryableCreate:          query.NewQueryableCreator(nil, nil, newProxyStoreWithTSDBStore(db), 2, timeout),
   757  		engineFactory:            ef,
   758  		defaultEngine:            PromqlEnginePrometheus,
   759  		lookbackDeltaCreate:      func(m int64) time.Duration { return time.Duration(0) },
   760  		gate:                     gate.New(nil, 4, gate.Queries),
   761  		defaultMetadataTimeRange: apiLookbackDelta,
   762  		queryRangeHist: promauto.With(prometheus.NewRegistry()).NewHistogram(prometheus.HistogramOpts{
   763  			Name: "query_range_hist",
   764  		}),
   765  		seriesStatsAggregatorFactory: &store.NoopSeriesStatsAggregatorFactory{},
   766  		tenantHeader:                 "thanos-tenant",
   767  		defaultTenant:                "default-tenant",
   768  	}
   769  
   770  	var tests = []endpointTestCase{
   771  		{
   772  			endpoint: api.labelValues,
   773  			params: map[string]string{
   774  				"name": "__name__",
   775  			},
   776  			response: []string{
   777  				"test_metric1",
   778  				"test_metric2",
   779  				"test_metric_replica1",
   780  				"test_metric_replica2",
   781  			},
   782  		},
   783  		{
   784  			endpoint: apiWithLabelLookback.labelValues,
   785  			params: map[string]string{
   786  				"name": "__name__",
   787  			},
   788  			response: []string{
   789  				"test_metric_replica1",
   790  				"test_metric_replica2",
   791  			},
   792  		},
   793  		{
   794  			endpoint: api.labelValues,
   795  			query: url.Values{
   796  				"start": []string{"1970-01-01T00:00:00Z"},
   797  				"end":   []string{"1970-01-01T00:09:00Z"},
   798  			},
   799  			params: map[string]string{
   800  				"name": "__name__",
   801  			},
   802  			response: []string{
   803  				"test_metric1",
   804  				"test_metric2",
   805  			},
   806  		},
   807  		{
   808  			endpoint: apiWithLabelLookback.labelValues,
   809  			query: url.Values{
   810  				"start": []string{"1970-01-01T00:00:00Z"},
   811  				"end":   []string{"1970-01-01T00:09:00Z"},
   812  			},
   813  			params: map[string]string{
   814  				"name": "__name__",
   815  			},
   816  			response: []string{
   817  				"test_metric1",
   818  				"test_metric2",
   819  			},
   820  		},
   821  		{
   822  			endpoint: api.labelNames,
   823  			response: []string{
   824  				"__name__",
   825  				"foo",
   826  				"replica",
   827  				"replica1",
   828  			},
   829  		},
   830  		{
   831  			endpoint: apiWithLabelLookback.labelNames,
   832  			response: []string{
   833  				"__name__",
   834  				"foo",
   835  				"replica",
   836  				"replica1",
   837  			},
   838  		},
   839  		{
   840  			endpoint: api.labelNames,
   841  			query: url.Values{
   842  				"start": []string{"1970-01-01T00:00:00Z"},
   843  				"end":   []string{"1970-01-01T00:09:00Z"},
   844  			},
   845  			response: []string{
   846  				"__name__",
   847  				"foo",
   848  			},
   849  		},
   850  		{
   851  			endpoint: apiWithLabelLookback.labelNames,
   852  			query: url.Values{
   853  				"start": []string{"1970-01-01T00:00:00Z"},
   854  				"end":   []string{"1970-01-01T00:09:00Z"},
   855  			},
   856  			response: []string{
   857  				"__name__",
   858  				"foo",
   859  			},
   860  		},
   861  		// Failed, to parse matchers.
   862  		{
   863  			endpoint: api.labelNames,
   864  			query: url.Values{
   865  				"match[]": []string{`{xxxx`},
   866  			},
   867  			errType: baseAPI.ErrorBadData,
   868  		},
   869  		// Failed to parse matchers.
   870  		{
   871  			endpoint: api.labelValues,
   872  			query: url.Values{
   873  				"match[]": []string{`{xxxx`},
   874  			},
   875  			params: map[string]string{
   876  				"name": "__name__",
   877  			},
   878  			errType: baseAPI.ErrorBadData,
   879  		},
   880  		{
   881  			endpoint: api.labelNames,
   882  			query: url.Values{
   883  				"match[]": []string{`test_metric_replica2`},
   884  			},
   885  			response: []string{"__name__", "foo", "replica1"},
   886  		},
   887  		{
   888  			endpoint: api.labelValues,
   889  			query: url.Values{
   890  				"match[]": []string{`test_metric_replica2`},
   891  			},
   892  			params: map[string]string{
   893  				"name": "__name__",
   894  			},
   895  			response: []string{"test_metric_replica2"},
   896  		},
   897  		{
   898  			endpoint: api.labelValues,
   899  			query: url.Values{
   900  				"match[]": []string{`{foo="bar"}`, `{foo="boo"}`},
   901  			},
   902  			params: map[string]string{
   903  				"name": "__name__",
   904  			},
   905  			response: []string{"test_metric1", "test_metric2", "test_metric_replica1", "test_metric_replica2"},
   906  		},
   907  		// No matched series.
   908  		{
   909  			endpoint: api.labelValues,
   910  			query: url.Values{
   911  				"match[]": []string{`{foo="yolo"}`},
   912  			},
   913  			params: map[string]string{
   914  				"name": "__name__",
   915  			},
   916  			response: []string{},
   917  		},
   918  		{
   919  			endpoint: api.labelValues,
   920  			query: url.Values{
   921  				"match[]": []string{`test_metric_replica2`},
   922  			},
   923  			params: map[string]string{
   924  				"name": "replica1",
   925  			},
   926  			response: []string{"a"},
   927  		},
   928  		// Bad name parameter.
   929  		{
   930  			endpoint: api.labelValues,
   931  			params: map[string]string{
   932  				"name": "not!!!allowed",
   933  			},
   934  			errType: baseAPI.ErrorBadData,
   935  		},
   936  		{
   937  			endpoint: api.series,
   938  			query: url.Values{
   939  				"match[]": []string{`test_metric2`},
   940  			},
   941  			response: []labels.Labels{
   942  				labels.FromStrings("__name__", "test_metric2", "foo", "boo"),
   943  			},
   944  		},
   945  		{
   946  			endpoint: apiWithLabelLookback.series,
   947  			query: url.Values{
   948  				"match[]": []string{`test_metric2`},
   949  			},
   950  			response: []labels.Labels{},
   951  		},
   952  		{
   953  			endpoint: apiWithLabelLookback.series,
   954  			query: url.Values{
   955  				"match[]": []string{`test_metric_replica1`},
   956  			},
   957  			response: []labels.Labels{
   958  				labels.FromStrings("__name__", "test_metric_replica1", "foo", "bar", "replica", "a"),
   959  				labels.FromStrings("__name__", "test_metric_replica1", "foo", "boo", "replica", "a"),
   960  				labels.FromStrings("__name__", "test_metric_replica1", "foo", "boo", "replica", "b"),
   961  			},
   962  		},
   963  		// Series that does not exist should return an empty array.
   964  		{
   965  			endpoint: api.series,
   966  			query: url.Values{
   967  				"match[]": []string{`foobar`},
   968  			},
   969  			response: []labels.Labels{},
   970  		},
   971  		{
   972  			endpoint: api.series,
   973  			query: url.Values{
   974  				"match[]": []string{`test_metric1{foo=~".+o"}`},
   975  			},
   976  			response: []labels.Labels{
   977  				labels.FromStrings("__name__", "test_metric1", "foo", "boo"),
   978  			},
   979  		},
   980  		{
   981  			endpoint: api.series,
   982  			query: url.Values{
   983  				"match[]": []string{`test_metric1{foo=~".+o$"}`, `test_metric1{foo=~".+o"}`},
   984  			},
   985  			response: []labels.Labels{
   986  				labels.FromStrings("__name__", "test_metric1", "foo", "boo"),
   987  			},
   988  		},
   989  		{
   990  			endpoint: api.series,
   991  			query: url.Values{
   992  				"match[]": []string{`test_metric1{foo=~".+o"}`, `none`},
   993  			},
   994  			response: []labels.Labels{
   995  				labels.FromStrings("__name__", "test_metric1", "foo", "boo"),
   996  			},
   997  		},
   998  		// Start and end before series starts.
   999  		{
  1000  			endpoint: api.series,
  1001  			query: url.Values{
  1002  				"match[]": []string{`test_metric2`},
  1003  				"start":   []string{"-2"},
  1004  				"end":     []string{"-1"},
  1005  			},
  1006  			response: []labels.Labels{},
  1007  		},
  1008  		// Start and end after series ends.
  1009  		{
  1010  			endpoint: api.series,
  1011  			query: url.Values{
  1012  				"match[]": []string{`test_metric2`},
  1013  				"start":   []string{"100000"},
  1014  				"end":     []string{"100001"},
  1015  			},
  1016  			response: []labels.Labels{},
  1017  		},
  1018  		// Start before series starts, end after series ends.
  1019  		{
  1020  			endpoint: api.series,
  1021  			query: url.Values{
  1022  				"match[]": []string{`test_metric2`},
  1023  				"start":   []string{"-1"},
  1024  				"end":     []string{"100000"},
  1025  			},
  1026  			response: []labels.Labels{
  1027  				labels.FromStrings("__name__", "test_metric2", "foo", "boo"),
  1028  			},
  1029  		},
  1030  		// Start and end within series.
  1031  		{
  1032  			endpoint: api.series,
  1033  			query: url.Values{
  1034  				"match[]": []string{`test_metric2`},
  1035  				"start":   []string{"1"},
  1036  				"end":     []string{"100"},
  1037  			},
  1038  			response: []labels.Labels{
  1039  				labels.FromStrings("__name__", "test_metric2", "foo", "boo"),
  1040  			},
  1041  		},
  1042  		// Start within series, end after.
  1043  		{
  1044  			endpoint: api.series,
  1045  			query: url.Values{
  1046  				"match[]": []string{`test_metric2`},
  1047  				"start":   []string{"1"},
  1048  				"end":     []string{"100000"},
  1049  			},
  1050  			response: []labels.Labels{
  1051  				labels.FromStrings("__name__", "test_metric2", "foo", "boo"),
  1052  			},
  1053  		},
  1054  		// Start before series, end within series.
  1055  		{
  1056  			endpoint: api.series,
  1057  			query: url.Values{
  1058  				"match[]": []string{`test_metric2`},
  1059  				"start":   []string{"-1"},
  1060  				"end":     []string{"1"},
  1061  			},
  1062  			response: []labels.Labels{
  1063  				labels.FromStrings("__name__", "test_metric2", "foo", "boo"),
  1064  			},
  1065  		},
  1066  		// Missing match[] query params in series requests.
  1067  		{
  1068  			endpoint: api.series,
  1069  			errType:  baseAPI.ErrorBadData,
  1070  		},
  1071  		{
  1072  			endpoint: api.series,
  1073  			query: url.Values{
  1074  				"match[]": []string{`test_metric2`},
  1075  				"dedup":   []string{"sdfsf-series"},
  1076  			},
  1077  			errType: baseAPI.ErrorBadData,
  1078  		},
  1079  		{
  1080  			endpoint: api.series,
  1081  			query: url.Values{
  1082  				"match[]": []string{`test_metric2`},
  1083  			},
  1084  			response: []labels.Labels{
  1085  				labels.FromStrings("__name__", "test_metric2", "foo", "boo"),
  1086  			},
  1087  			method: http.MethodPost,
  1088  		},
  1089  		{
  1090  			endpoint: api.series,
  1091  			query: url.Values{
  1092  				"match[]": []string{`test_metric1{foo=~".+o"}`},
  1093  			},
  1094  			response: []labels.Labels{
  1095  				labels.FromStrings("__name__", "test_metric1", "foo", "boo"),
  1096  			},
  1097  			method: http.MethodPost,
  1098  		},
  1099  		{
  1100  			endpoint: api.series,
  1101  			query: url.Values{
  1102  				"match[]": []string{`test_metric1{foo=~".+o$"}`, `test_metric1{foo=~".+o"}`},
  1103  			},
  1104  			response: []labels.Labels{
  1105  				labels.FromStrings("__name__", "test_metric1", "foo", "boo"),
  1106  			},
  1107  			method: http.MethodPost,
  1108  		},
  1109  		{
  1110  			endpoint: api.series,
  1111  			query: url.Values{
  1112  				"match[]": []string{`test_metric1{foo=~".+o"}`, `none`},
  1113  			},
  1114  			response: []labels.Labels{
  1115  				labels.FromStrings("__name__", "test_metric1", "foo", "boo"),
  1116  			},
  1117  			method: http.MethodPost,
  1118  		},
  1119  		// Start and end before series starts.
  1120  		{
  1121  			endpoint: api.series,
  1122  			query: url.Values{
  1123  				"match[]": []string{`test_metric2`},
  1124  				"start":   []string{"-2"},
  1125  				"end":     []string{"-1"},
  1126  			},
  1127  			response: []labels.Labels{},
  1128  		},
  1129  		// Start and end after series ends.
  1130  		{
  1131  			endpoint: api.series,
  1132  			query: url.Values{
  1133  				"match[]": []string{`test_metric2`},
  1134  				"start":   []string{"100000"},
  1135  				"end":     []string{"100001"},
  1136  			},
  1137  			response: []labels.Labels{},
  1138  		},
  1139  		// Start before series starts, end after series ends.
  1140  		{
  1141  			endpoint: api.series,
  1142  			query: url.Values{
  1143  				"match[]": []string{`test_metric2`},
  1144  				"start":   []string{"-1"},
  1145  				"end":     []string{"100000"},
  1146  			},
  1147  			response: []labels.Labels{
  1148  				labels.FromStrings("__name__", "test_metric2", "foo", "boo"),
  1149  			},
  1150  			method: http.MethodPost,
  1151  		},
  1152  		// Start and end within series.
  1153  		{
  1154  			endpoint: api.series,
  1155  			query: url.Values{
  1156  				"match[]": []string{`test_metric2`},
  1157  				"start":   []string{"1"},
  1158  				"end":     []string{"100"},
  1159  			},
  1160  			response: []labels.Labels{
  1161  				labels.FromStrings("__name__", "test_metric2", "foo", "boo"),
  1162  			},
  1163  			method: http.MethodPost,
  1164  		},
  1165  		// Start within series, end after.
  1166  		{
  1167  			endpoint: api.series,
  1168  			query: url.Values{
  1169  				"match[]": []string{`test_metric2`},
  1170  				"start":   []string{"1"},
  1171  				"end":     []string{"100000"},
  1172  			},
  1173  			response: []labels.Labels{
  1174  				labels.FromStrings("__name__", "test_metric2", "foo", "boo"),
  1175  			},
  1176  			method: http.MethodPost,
  1177  		},
  1178  		// Start before series, end within series.
  1179  		{
  1180  			endpoint: api.series,
  1181  			query: url.Values{
  1182  				"match[]": []string{`test_metric2`},
  1183  				"start":   []string{"-1"},
  1184  				"end":     []string{"1"},
  1185  			},
  1186  			response: []labels.Labels{
  1187  				labels.FromStrings("__name__", "test_metric2", "foo", "boo"),
  1188  			},
  1189  			method: http.MethodPost,
  1190  		},
  1191  		// Missing match[] query params in series requests.
  1192  		{
  1193  			endpoint: api.series,
  1194  			errType:  baseAPI.ErrorBadData,
  1195  			method:   http.MethodPost,
  1196  		},
  1197  		{
  1198  			endpoint: api.series,
  1199  			query: url.Values{
  1200  				"match[]": []string{`test_metric2`},
  1201  				"dedup":   []string{"sdfsf-series"},
  1202  			},
  1203  			errType: baseAPI.ErrorBadData,
  1204  			method:  http.MethodPost,
  1205  		},
  1206  	}
  1207  
  1208  	for i, test := range tests {
  1209  		if ok := testEndpoint(t, test, strings.TrimSpace(fmt.Sprintf("#%d %s", i, test.query.Encode())), reflect.DeepEqual); !ok {
  1210  			return
  1211  		}
  1212  	}
  1213  }
  1214  
  1215  func TestStoresEndpoint(t *testing.T) {
  1216  	apiWithNotEndpoints := &QueryAPI{
  1217  		endpointStatus: func() []query.EndpointStatus {
  1218  			return []query.EndpointStatus{}
  1219  		},
  1220  	}
  1221  	apiWithValidEndpoints := &QueryAPI{
  1222  		endpointStatus: func() []query.EndpointStatus {
  1223  			return []query.EndpointStatus{
  1224  				{
  1225  					Name:          "endpoint-1",
  1226  					ComponentType: component.Store,
  1227  				},
  1228  				{
  1229  					Name:          "endpoint-2",
  1230  					ComponentType: component.Store,
  1231  				},
  1232  				{
  1233  					Name:          "endpoint-3",
  1234  					ComponentType: component.Sidecar,
  1235  				},
  1236  			}
  1237  		},
  1238  		tenantHeader:  "thanos-tenant",
  1239  		defaultTenant: "default-tenant",
  1240  	}
  1241  	apiWithInvalidEndpoint := &QueryAPI{
  1242  		endpointStatus: func() []query.EndpointStatus {
  1243  			return []query.EndpointStatus{
  1244  				{
  1245  					Name:          "endpoint-1",
  1246  					ComponentType: component.Store,
  1247  				},
  1248  				{
  1249  					Name: "endpoint-2",
  1250  				},
  1251  			}
  1252  		},
  1253  	}
  1254  
  1255  	testCases := []endpointTestCase{
  1256  		{
  1257  			endpoint: apiWithNotEndpoints.stores,
  1258  			method:   http.MethodGet,
  1259  			response: map[string][]query.EndpointStatus{},
  1260  		},
  1261  		{
  1262  			endpoint: apiWithValidEndpoints.stores,
  1263  			method:   http.MethodGet,
  1264  			response: map[string][]query.EndpointStatus{
  1265  				"store": {
  1266  					{
  1267  						Name:          "endpoint-1",
  1268  						ComponentType: component.Store,
  1269  					},
  1270  					{
  1271  						Name:          "endpoint-2",
  1272  						ComponentType: component.Store,
  1273  					},
  1274  				},
  1275  				"sidecar": {
  1276  					{
  1277  						Name:          "endpoint-3",
  1278  						ComponentType: component.Sidecar,
  1279  					},
  1280  				},
  1281  			},
  1282  		},
  1283  		{
  1284  			endpoint: apiWithInvalidEndpoint.stores,
  1285  			method:   http.MethodGet,
  1286  			response: map[string][]query.EndpointStatus{
  1287  				"store": {
  1288  					{
  1289  						Name:          "endpoint-1",
  1290  						ComponentType: component.Store,
  1291  					},
  1292  				},
  1293  			},
  1294  		},
  1295  	}
  1296  
  1297  	for i, test := range testCases {
  1298  		if ok := testEndpoint(t, test, strings.TrimSpace(fmt.Sprintf("#%d %s", i, test.query.Encode())), reflect.DeepEqual); !ok {
  1299  			return
  1300  		}
  1301  	}
  1302  }
  1303  
  1304  func TestParseTime(t *testing.T) {
  1305  	ts, err := time.Parse(time.RFC3339Nano, "2015-06-03T13:21:58.555Z")
  1306  	if err != nil {
  1307  		panic(err)
  1308  	}
  1309  
  1310  	var tests = []struct {
  1311  		input  string
  1312  		fail   bool
  1313  		result time.Time
  1314  	}{
  1315  		{
  1316  			input: "",
  1317  			fail:  true,
  1318  		}, {
  1319  			input: "abc",
  1320  			fail:  true,
  1321  		}, {
  1322  			input: "30s",
  1323  			fail:  true,
  1324  		}, {
  1325  			input:  "123",
  1326  			result: time.Unix(123, 0),
  1327  		}, {
  1328  			input:  "123.123",
  1329  			result: time.Unix(123, 123000000),
  1330  		}, {
  1331  			input:  "2015-06-03T13:21:58.555Z",
  1332  			result: ts,
  1333  		}, {
  1334  			input:  "2015-06-03T14:21:58.555+01:00",
  1335  			result: ts,
  1336  		}, {
  1337  			// Test float rounding.
  1338  			input:  "1543578564.705",
  1339  			result: time.Unix(1543578564, 705*1e6),
  1340  		},
  1341  	}
  1342  
  1343  	for _, test := range tests {
  1344  		ts, err := parseTime(test.input)
  1345  		if err != nil && !test.fail {
  1346  			t.Errorf("Unexpected error for %q: %s", test.input, err)
  1347  			continue
  1348  		}
  1349  		if err == nil && test.fail {
  1350  			t.Errorf("Expected error for %q but got none", test.input)
  1351  			continue
  1352  		}
  1353  		if !test.fail && !ts.Equal(test.result) {
  1354  			t.Errorf("Expected time %v for input %q but got %v", test.result, test.input, ts)
  1355  		}
  1356  	}
  1357  }
  1358  
  1359  func TestParseDuration(t *testing.T) {
  1360  	var tests = []struct {
  1361  		input  string
  1362  		fail   bool
  1363  		result time.Duration
  1364  	}{
  1365  		{
  1366  			input: "",
  1367  			fail:  true,
  1368  		}, {
  1369  			input: "abc",
  1370  			fail:  true,
  1371  		}, {
  1372  			input: "2015-06-03T13:21:58.555Z",
  1373  			fail:  true,
  1374  		}, {
  1375  			// Internal int64 overflow.
  1376  			input: "-148966367200.372",
  1377  			fail:  true,
  1378  		}, {
  1379  			// Internal int64 overflow.
  1380  			input: "148966367200.372",
  1381  			fail:  true,
  1382  		}, {
  1383  			input:  "123",
  1384  			result: 123 * time.Second,
  1385  		}, {
  1386  			input:  "123.333",
  1387  			result: 123*time.Second + 333*time.Millisecond,
  1388  		}, {
  1389  			input:  "15s",
  1390  			result: 15 * time.Second,
  1391  		}, {
  1392  			input:  "5m",
  1393  			result: 5 * time.Minute,
  1394  		},
  1395  	}
  1396  
  1397  	for _, test := range tests {
  1398  		d, err := parseDuration(test.input)
  1399  		if err != nil && !test.fail {
  1400  			t.Errorf("Unexpected error for %q: %s", test.input, err)
  1401  			continue
  1402  		}
  1403  		if err == nil && test.fail {
  1404  			t.Errorf("Expected error for %q but got none", test.input)
  1405  			continue
  1406  		}
  1407  		if !test.fail && d != test.result {
  1408  			t.Errorf("Expected duration %v for input %q but got %v", test.result, test.input, d)
  1409  		}
  1410  	}
  1411  }
  1412  
  1413  func TestParseDownsamplingParamMillis(t *testing.T) {
  1414  	var tests = []struct {
  1415  		maxSourceResolutionParam string
  1416  		result                   int64
  1417  		step                     time.Duration
  1418  		fail                     bool
  1419  		enableAutodownsampling   bool
  1420  	}{
  1421  		{
  1422  			maxSourceResolutionParam: "0s",
  1423  			enableAutodownsampling:   false,
  1424  			step:                     time.Hour,
  1425  			result:                   int64(compact.ResolutionLevelRaw),
  1426  			fail:                     false,
  1427  		},
  1428  		{
  1429  			maxSourceResolutionParam: "5m",
  1430  			step:                     time.Hour,
  1431  			enableAutodownsampling:   false,
  1432  			result:                   int64(compact.ResolutionLevel5m),
  1433  			fail:                     false,
  1434  		},
  1435  		{
  1436  			maxSourceResolutionParam: "1h",
  1437  			step:                     time.Hour,
  1438  			enableAutodownsampling:   false,
  1439  			result:                   int64(compact.ResolutionLevel1h),
  1440  			fail:                     false,
  1441  		},
  1442  		{
  1443  			maxSourceResolutionParam: "",
  1444  			enableAutodownsampling:   true,
  1445  			step:                     time.Hour,
  1446  			result:                   int64(time.Hour / (5 * 1000 * 1000)),
  1447  			fail:                     false,
  1448  		},
  1449  		{
  1450  			maxSourceResolutionParam: "",
  1451  			enableAutodownsampling:   true,
  1452  			step:                     time.Hour,
  1453  			result:                   int64((1 * time.Hour) / 6),
  1454  			fail:                     true,
  1455  		},
  1456  		{
  1457  			maxSourceResolutionParam: "",
  1458  			enableAutodownsampling:   true,
  1459  			step:                     time.Hour,
  1460  			result:                   int64((1 * time.Hour) / 6),
  1461  			fail:                     true,
  1462  		},
  1463  		// maxSourceResolution param can be overwritten.
  1464  		{
  1465  			maxSourceResolutionParam: "1m",
  1466  			enableAutodownsampling:   true,
  1467  			step:                     time.Hour,
  1468  			result:                   int64(time.Minute / (1000 * 1000)),
  1469  			fail:                     false,
  1470  		},
  1471  	}
  1472  
  1473  	for i, test := range tests {
  1474  		api := QueryAPI{
  1475  			enableAutodownsampling: test.enableAutodownsampling,
  1476  			gate:                   gate.New(nil, 4, gate.Queries),
  1477  			queryRangeHist: promauto.With(prometheus.NewRegistry()).NewHistogram(prometheus.HistogramOpts{
  1478  				Name: "query_range_hist",
  1479  			}),
  1480  		}
  1481  		v := url.Values{}
  1482  		v.Set(MaxSourceResolutionParam, test.maxSourceResolutionParam)
  1483  		r := http.Request{PostForm: v}
  1484  
  1485  		// If no max_source_resolution is specified fit at least 5 samples between steps.
  1486  		maxResMillis, _ := api.parseDownsamplingParamMillis(&r, test.step/5)
  1487  		if test.fail == false {
  1488  			testutil.Assert(t, maxResMillis == test.result, "case %v: expected %v to be equal to %v", i, maxResMillis, test.result)
  1489  		} else {
  1490  			testutil.Assert(t, maxResMillis != test.result, "case %v: expected %v not to be equal to %v", i, maxResMillis, test.result)
  1491  		}
  1492  
  1493  	}
  1494  }
  1495  
  1496  func TestParseStoreDebugMatchersParam(t *testing.T) {
  1497  	for i, tc := range []struct {
  1498  		storeMatchers string
  1499  		fail          bool
  1500  		result        [][]*labels.Matcher
  1501  	}{
  1502  		{
  1503  			storeMatchers: "123",
  1504  			fail:          true,
  1505  		},
  1506  		{
  1507  			storeMatchers: "foo",
  1508  			fail:          false,
  1509  			result:        [][]*labels.Matcher{{labels.MustNewMatcher(labels.MatchEqual, "__name__", "foo")}},
  1510  		},
  1511  		{
  1512  			storeMatchers: `{__address__="localhost:10905"}`,
  1513  			fail:          false,
  1514  			result:        [][]*labels.Matcher{{labels.MustNewMatcher(labels.MatchEqual, "__address__", "localhost:10905")}},
  1515  		},
  1516  		{
  1517  			storeMatchers: `{__address__="localhost:10905", cluster="test"}`,
  1518  			fail:          false,
  1519  			result: [][]*labels.Matcher{{
  1520  				labels.MustNewMatcher(labels.MatchEqual, "__address__", "localhost:10905"),
  1521  				labels.MustNewMatcher(labels.MatchEqual, "cluster", "test"),
  1522  			}},
  1523  		},
  1524  	} {
  1525  		t.Run(fmt.Sprintf("%d", i), func(t *testing.T) {
  1526  			api := QueryAPI{
  1527  				gate: promgate.New(4),
  1528  				queryRangeHist: promauto.With(prometheus.NewRegistry()).NewHistogram(prometheus.HistogramOpts{
  1529  					Name: "query_range_hist",
  1530  				}),
  1531  			}
  1532  			v := url.Values{}
  1533  			v.Set(StoreMatcherParam, tc.storeMatchers)
  1534  			r := &http.Request{PostForm: v}
  1535  
  1536  			storeMatchers, err := api.parseStoreDebugMatchersParam(r)
  1537  			if !tc.fail {
  1538  				testutil.Equals(t, tc.result, storeMatchers)
  1539  				testutil.Equals(t, (*baseAPI.ApiError)(nil), err)
  1540  			} else {
  1541  				testutil.NotOk(t, err)
  1542  			}
  1543  		})
  1544  	}
  1545  }
  1546  
  1547  func TestRulesHandler(t *testing.T) {
  1548  	twoHAgo := time.Now().Add(-2 * time.Hour)
  1549  	all := []*rulespb.Rule{
  1550  		rulespb.NewRecordingRule(&rulespb.RecordingRule{
  1551  			Name:                      "1",
  1552  			LastEvaluation:            time.Time{}.Add(1 * time.Minute),
  1553  			EvaluationDurationSeconds: 12,
  1554  			Health:                    "x",
  1555  			Query:                     "sum(up)",
  1556  			Labels:                    labelpb.ZLabelSet{Labels: []labelpb.ZLabel{{Name: "some", Value: "label"}}},
  1557  			LastError:                 "err1",
  1558  		}),
  1559  		rulespb.NewRecordingRule(&rulespb.RecordingRule{
  1560  			Name:                      "2",
  1561  			LastEvaluation:            time.Time{}.Add(2 * time.Minute),
  1562  			EvaluationDurationSeconds: 12,
  1563  			Health:                    "x",
  1564  			Query:                     "sum(up1)",
  1565  			Labels:                    labelpb.ZLabelSet{Labels: []labelpb.ZLabel{{Name: "some", Value: "label2"}}},
  1566  		}),
  1567  		rulespb.NewAlertingRule(&rulespb.Alert{
  1568  			Name:                      "3",
  1569  			LastEvaluation:            time.Time{}.Add(3 * time.Minute),
  1570  			EvaluationDurationSeconds: 12,
  1571  			Health:                    "x",
  1572  			Query:                     "sum(up2) == 2",
  1573  			DurationSeconds:           101,
  1574  			Labels:                    labelpb.ZLabelSet{Labels: []labelpb.ZLabel{{Name: "some", Value: "label3"}}},
  1575  			Annotations:               labelpb.ZLabelSet{Labels: []labelpb.ZLabel{{Name: "ann", Value: "a1"}}},
  1576  			Alerts: []*rulespb.AlertInstance{
  1577  				{
  1578  					Labels:      labelpb.ZLabelSet{Labels: []labelpb.ZLabel{{Name: "inside", Value: "1"}}},
  1579  					Annotations: labelpb.ZLabelSet{Labels: []labelpb.ZLabel{{Name: "insideann", Value: "2"}}},
  1580  					State:       rulespb.AlertState_FIRING,
  1581  					ActiveAt:    &twoHAgo,
  1582  					Value:       "1",
  1583  					// This is unlikely if groups is warn, but test nevertheless.
  1584  					PartialResponseStrategy: storepb.PartialResponseStrategy_ABORT,
  1585  				},
  1586  				{
  1587  					Labels:      labelpb.ZLabelSet{Labels: []labelpb.ZLabel{{Name: "inside", Value: "3"}}},
  1588  					Annotations: labelpb.ZLabelSet{Labels: []labelpb.ZLabel{{Name: "insideann", Value: "4"}}},
  1589  					State:       rulespb.AlertState_PENDING,
  1590  					ActiveAt:    nil,
  1591  					Value:       "2",
  1592  					// This is unlikely if groups is warn, but test nevertheless.
  1593  					PartialResponseStrategy: storepb.PartialResponseStrategy_ABORT,
  1594  				},
  1595  			},
  1596  			State: rulespb.AlertState_FIRING,
  1597  		}),
  1598  		rulespb.NewAlertingRule(&rulespb.Alert{
  1599  			Name:                      "4",
  1600  			LastEvaluation:            time.Time{}.Add(4 * time.Minute),
  1601  			EvaluationDurationSeconds: 122,
  1602  			Health:                    "x",
  1603  			DurationSeconds:           102,
  1604  			Query:                     "sum(up3) == 3",
  1605  			Labels:                    labelpb.ZLabelSet{Labels: []labelpb.ZLabel{{Name: "some", Value: "label4"}}},
  1606  			State:                     rulespb.AlertState_INACTIVE,
  1607  		}),
  1608  	}
  1609  
  1610  	endpoint := NewRulesHandler(mockedRulesClient{
  1611  		g: map[rulespb.RulesRequest_Type][]*rulespb.RuleGroup{
  1612  			rulespb.RulesRequest_ALL: {
  1613  				{
  1614  					Name:                      "grp",
  1615  					File:                      "/path/to/groupfile1",
  1616  					Rules:                     all,
  1617  					Interval:                  1,
  1618  					EvaluationDurationSeconds: 214,
  1619  					LastEvaluation:            time.Time{}.Add(10 * time.Minute),
  1620  					PartialResponseStrategy:   storepb.PartialResponseStrategy_WARN,
  1621  				},
  1622  				{
  1623  					Name:                      "grp2",
  1624  					File:                      "/path/to/groupfile2",
  1625  					Rules:                     all[3:],
  1626  					Interval:                  10,
  1627  					EvaluationDurationSeconds: 2142,
  1628  					LastEvaluation:            time.Time{}.Add(100 * time.Minute),
  1629  					PartialResponseStrategy:   storepb.PartialResponseStrategy_ABORT,
  1630  				},
  1631  			},
  1632  			rulespb.RulesRequest_RECORD: {
  1633  				{
  1634  					Name:                      "grp",
  1635  					File:                      "/path/to/groupfile1",
  1636  					Rules:                     all[:2],
  1637  					Interval:                  1,
  1638  					EvaluationDurationSeconds: 214,
  1639  					LastEvaluation:            time.Time{}.Add(20 * time.Minute),
  1640  					PartialResponseStrategy:   storepb.PartialResponseStrategy_WARN,
  1641  				},
  1642  			},
  1643  			rulespb.RulesRequest_ALERT: {
  1644  				{
  1645  					Name:                      "grp",
  1646  					File:                      "/path/to/groupfile1",
  1647  					Rules:                     all[2:],
  1648  					Interval:                  1,
  1649  					EvaluationDurationSeconds: 214,
  1650  					LastEvaluation:            time.Time{}.Add(30 * time.Minute),
  1651  					PartialResponseStrategy:   storepb.PartialResponseStrategy_WARN,
  1652  				},
  1653  			},
  1654  		},
  1655  	}, false)
  1656  
  1657  	type test struct {
  1658  		params   map[string]string
  1659  		query    url.Values
  1660  		response interface{}
  1661  	}
  1662  	expectedAll := []testpromcompatibility.Rule{
  1663  		testpromcompatibility.RecordingRule{
  1664  			Name:           all[0].GetRecording().Name,
  1665  			Query:          all[0].GetRecording().Query,
  1666  			Labels:         labelpb.ZLabelsToPromLabels(all[0].GetRecording().Labels.Labels),
  1667  			Health:         rules.RuleHealth(all[0].GetRecording().Health),
  1668  			LastError:      all[0].GetRecording().LastError,
  1669  			LastEvaluation: all[0].GetRecording().LastEvaluation,
  1670  			EvaluationTime: all[0].GetRecording().EvaluationDurationSeconds,
  1671  			Type:           "recording",
  1672  		},
  1673  		testpromcompatibility.RecordingRule{
  1674  			Name:           all[1].GetRecording().Name,
  1675  			Query:          all[1].GetRecording().Query,
  1676  			Labels:         labelpb.ZLabelsToPromLabels(all[1].GetRecording().Labels.Labels),
  1677  			Health:         rules.RuleHealth(all[1].GetRecording().Health),
  1678  			LastError:      all[1].GetRecording().LastError,
  1679  			LastEvaluation: all[1].GetRecording().LastEvaluation,
  1680  			EvaluationTime: all[1].GetRecording().EvaluationDurationSeconds,
  1681  			Type:           "recording",
  1682  		},
  1683  		testpromcompatibility.AlertingRule{
  1684  			State:          strings.ToLower(all[2].GetAlert().State.String()),
  1685  			Name:           all[2].GetAlert().Name,
  1686  			Query:          all[2].GetAlert().Query,
  1687  			Labels:         labelpb.ZLabelsToPromLabels(all[2].GetAlert().Labels.Labels),
  1688  			Health:         rules.RuleHealth(all[2].GetAlert().Health),
  1689  			LastError:      all[2].GetAlert().LastError,
  1690  			LastEvaluation: all[2].GetAlert().LastEvaluation,
  1691  			EvaluationTime: all[2].GetAlert().EvaluationDurationSeconds,
  1692  			Duration:       all[2].GetAlert().DurationSeconds,
  1693  			Annotations:    labelpb.ZLabelsToPromLabels(all[2].GetAlert().Annotations.Labels),
  1694  			Alerts: []*testpromcompatibility.Alert{
  1695  				{
  1696  					Labels:                  labelpb.ZLabelsToPromLabels(all[2].GetAlert().Alerts[0].Labels.Labels),
  1697  					Annotations:             labelpb.ZLabelsToPromLabels(all[2].GetAlert().Alerts[0].Annotations.Labels),
  1698  					State:                   strings.ToLower(all[2].GetAlert().Alerts[0].State.String()),
  1699  					ActiveAt:                all[2].GetAlert().Alerts[0].ActiveAt,
  1700  					Value:                   all[2].GetAlert().Alerts[0].Value,
  1701  					PartialResponseStrategy: all[2].GetAlert().Alerts[0].PartialResponseStrategy.String(),
  1702  				},
  1703  				{
  1704  					Labels:                  labelpb.ZLabelsToPromLabels(all[2].GetAlert().Alerts[1].Labels.Labels),
  1705  					Annotations:             labelpb.ZLabelsToPromLabels(all[2].GetAlert().Alerts[1].Annotations.Labels),
  1706  					State:                   strings.ToLower(all[2].GetAlert().Alerts[1].State.String()),
  1707  					ActiveAt:                all[2].GetAlert().Alerts[1].ActiveAt,
  1708  					Value:                   all[2].GetAlert().Alerts[1].Value,
  1709  					PartialResponseStrategy: all[2].GetAlert().Alerts[1].PartialResponseStrategy.String(),
  1710  				},
  1711  			},
  1712  			Type: "alerting",
  1713  		},
  1714  		testpromcompatibility.AlertingRule{
  1715  			State:          strings.ToLower(all[3].GetAlert().State.String()),
  1716  			Name:           all[3].GetAlert().Name,
  1717  			Query:          all[3].GetAlert().Query,
  1718  			Labels:         labelpb.ZLabelsToPromLabels(all[3].GetAlert().Labels.Labels),
  1719  			Health:         rules.RuleHealth(all[2].GetAlert().Health),
  1720  			LastError:      all[3].GetAlert().LastError,
  1721  			LastEvaluation: all[3].GetAlert().LastEvaluation,
  1722  			EvaluationTime: all[3].GetAlert().EvaluationDurationSeconds,
  1723  			Duration:       all[3].GetAlert().DurationSeconds,
  1724  			Annotations:    nil,
  1725  			Alerts:         []*testpromcompatibility.Alert{},
  1726  			Type:           "alerting",
  1727  		},
  1728  	}
  1729  	for _, test := range []test{
  1730  		{
  1731  			response: &testpromcompatibility.RuleDiscovery{
  1732  				RuleGroups: []*testpromcompatibility.RuleGroup{
  1733  					{
  1734  						Name:                    "grp",
  1735  						File:                    "/path/to/groupfile1",
  1736  						Rules:                   expectedAll,
  1737  						Interval:                1,
  1738  						EvaluationTime:          214,
  1739  						LastEvaluation:          time.Time{}.Add(10 * time.Minute),
  1740  						PartialResponseStrategy: "WARN",
  1741  					},
  1742  					{
  1743  						Name:                    "grp2",
  1744  						File:                    "/path/to/groupfile2",
  1745  						Rules:                   expectedAll[3:],
  1746  						Interval:                10,
  1747  						EvaluationTime:          2142,
  1748  						LastEvaluation:          time.Time{}.Add(100 * time.Minute),
  1749  						PartialResponseStrategy: "ABORT",
  1750  					},
  1751  				},
  1752  			},
  1753  		},
  1754  		{
  1755  			query: url.Values{"type": []string{"record"}},
  1756  			response: &testpromcompatibility.RuleDiscovery{
  1757  				RuleGroups: []*testpromcompatibility.RuleGroup{
  1758  					{
  1759  						Name:                    "grp",
  1760  						File:                    "/path/to/groupfile1",
  1761  						Rules:                   expectedAll[:2],
  1762  						Interval:                1,
  1763  						EvaluationTime:          214,
  1764  						LastEvaluation:          time.Time{}.Add(20 * time.Minute),
  1765  						PartialResponseStrategy: "WARN",
  1766  					},
  1767  				},
  1768  			},
  1769  		},
  1770  		{
  1771  			query: url.Values{"type": []string{"alert"}},
  1772  			response: &testpromcompatibility.RuleDiscovery{
  1773  				RuleGroups: []*testpromcompatibility.RuleGroup{
  1774  					{
  1775  						Name:                    "grp",
  1776  						File:                    "/path/to/groupfile1",
  1777  						Rules:                   expectedAll[2:],
  1778  						Interval:                1,
  1779  						EvaluationTime:          214,
  1780  						LastEvaluation:          time.Time{}.Add(30 * time.Minute),
  1781  						PartialResponseStrategy: "WARN",
  1782  					},
  1783  				},
  1784  			},
  1785  		},
  1786  	} {
  1787  		t.Run(fmt.Sprintf("endpoint=%s/method=%s/query=%q", "rules", http.MethodGet, test.query.Encode()), func(t *testing.T) {
  1788  			// Build a context with the correct request params.
  1789  			ctx := context.Background()
  1790  			for p, v := range test.params {
  1791  				ctx = route.WithParam(ctx, p, v)
  1792  			}
  1793  
  1794  			req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("http://example.com?%s", test.query.Encode()), nil)
  1795  			if err != nil {
  1796  				t.Fatal(err)
  1797  			}
  1798  			res, errors, apiError, releaseResources := endpoint(req.WithContext(ctx))
  1799  			defer releaseResources()
  1800  			if errors != nil {
  1801  				t.Fatalf("Unexpected errors: %s", errors)
  1802  				return
  1803  			}
  1804  			testutil.Assert(t, apiError == nil, "unexpected error %v", apiError)
  1805  
  1806  			// Those are different types now, but let's JSON outputs.
  1807  			got, err := json.MarshalIndent(res, "", " ")
  1808  			testutil.Ok(t, err)
  1809  			exp, err := json.MarshalIndent(test.response, "", " ")
  1810  			testutil.Ok(t, err)
  1811  
  1812  			testutil.Equals(t, string(exp), string(got))
  1813  		})
  1814  	}
  1815  }
  1816  
  1817  func BenchmarkQueryResultEncoding(b *testing.B) {
  1818  	var mat promql.Matrix
  1819  	for i := 0; i < 1000; i++ {
  1820  		lset := labels.FromStrings(
  1821  			"__name__", "my_test_metric_name",
  1822  			"instance", fmt.Sprintf("abcdefghijklmnopqrstuvxyz-%d", i),
  1823  			"job", "test-test",
  1824  			"method", "ABCD",
  1825  			"status", "199",
  1826  			"namespace", "something",
  1827  			"long-label", "34grnt83j0qxj309je9rgt9jf2jd-92jd-92jf9wrfjre",
  1828  		)
  1829  		var points []promql.FPoint
  1830  		for j := 0; j < b.N/1000; j++ {
  1831  			points = append(points, promql.FPoint{
  1832  				T: int64(j * 10000),
  1833  				F: rand.Float64(),
  1834  			})
  1835  		}
  1836  		mat = append(mat, promql.Series{
  1837  			Metric: lset,
  1838  			Floats: points,
  1839  		})
  1840  	}
  1841  	input := &queryData{
  1842  		ResultType: parser.ValueTypeMatrix,
  1843  		Result:     mat,
  1844  	}
  1845  	b.ResetTimer()
  1846  
  1847  	_, err := json.Marshal(&input)
  1848  	testutil.Ok(b, err)
  1849  }
  1850  
  1851  type mockedRulesClient struct {
  1852  	g   map[rulespb.RulesRequest_Type][]*rulespb.RuleGroup
  1853  	w   storage.Warnings
  1854  	err error
  1855  }
  1856  
  1857  func (c mockedRulesClient) Rules(_ context.Context, req *rulespb.RulesRequest) (*rulespb.RuleGroups, storage.Warnings, error) {
  1858  	return &rulespb.RuleGroups{Groups: c.g[req.Type]}, c.w, c.err
  1859  }
  1860  
  1861  type sample struct {
  1862  	t int64
  1863  	f float64
  1864  }
  1865  
  1866  func (s sample) T() int64 {
  1867  	return s.t
  1868  }
  1869  
  1870  func (s sample) F() float64 {
  1871  	return s.f
  1872  }
  1873  
  1874  // TODO(rabenhorst): Needs to be implemented for native histogram support.
  1875  func (s sample) H() *histogram.Histogram {
  1876  	panic("not implemented")
  1877  }
  1878  
  1879  func (s sample) FH() *histogram.FloatHistogram {
  1880  	panic("not implemented")
  1881  }
  1882  
  1883  func (s sample) Type() chunkenc.ValueType {
  1884  	return chunkenc.ValFloat
  1885  }