1// Copyright 2016 The Prometheus Authors
2// Licensed under the Apache License, Version 2.0 (the "License");
3// you may not use this file except in compliance with the License.
4// You may obtain a copy of the License at
5//
6//     http://www.apache.org/licenses/LICENSE-2.0
7//
8// Unless required by applicable law or agreed to in writing, software
9// distributed under the License is distributed on an "AS IS" BASIS,
10// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11// See the License for the specific language governing permissions and
12// limitations under the License.
13
14package v1
15
16import (
17	"context"
18	"encoding/json"
19	"fmt"
20	"io/ioutil"
21	"math"
22	"net/http"
23	"net/http/httptest"
24	"net/url"
25	"os"
26	"reflect"
27	"runtime"
28	"sort"
29	"strings"
30	"testing"
31	"time"
32
33	"github.com/go-kit/log"
34	"github.com/pkg/errors"
35	"github.com/prometheus/client_golang/prometheus"
36	config_util "github.com/prometheus/common/config"
37	"github.com/prometheus/common/model"
38	"github.com/prometheus/common/promlog"
39	"github.com/prometheus/common/route"
40	"github.com/stretchr/testify/require"
41
42	"github.com/prometheus/prometheus/config"
43	"github.com/prometheus/prometheus/pkg/exemplar"
44	"github.com/prometheus/prometheus/pkg/labels"
45	"github.com/prometheus/prometheus/pkg/textparse"
46	"github.com/prometheus/prometheus/pkg/timestamp"
47	"github.com/prometheus/prometheus/prompb"
48	"github.com/prometheus/prometheus/promql"
49	"github.com/prometheus/prometheus/promql/parser"
50	"github.com/prometheus/prometheus/rules"
51	"github.com/prometheus/prometheus/scrape"
52	"github.com/prometheus/prometheus/storage"
53	"github.com/prometheus/prometheus/storage/remote"
54	"github.com/prometheus/prometheus/tsdb"
55	"github.com/prometheus/prometheus/util/teststorage"
56)
57
58// testMetaStore satisfies the scrape.MetricMetadataStore interface.
59// It is used to inject specific metadata as part of a test case.
60type testMetaStore struct {
61	Metadata []scrape.MetricMetadata
62}
63
64func (s *testMetaStore) ListMetadata() []scrape.MetricMetadata {
65	return s.Metadata
66}
67
68func (s *testMetaStore) GetMetadata(metric string) (scrape.MetricMetadata, bool) {
69	for _, m := range s.Metadata {
70		if metric == m.Metric {
71			return m, true
72		}
73	}
74
75	return scrape.MetricMetadata{}, false
76}
77
78func (s *testMetaStore) SizeMetadata() int   { return 0 }
79func (s *testMetaStore) LengthMetadata() int { return 0 }
80
81// testTargetRetriever represents a list of targets to scrape.
82// It is used to represent targets as part of test cases.
83type testTargetRetriever struct {
84	activeTargets  map[string][]*scrape.Target
85	droppedTargets map[string][]*scrape.Target
86}
87
88type testTargetParams struct {
89	Identifier       string
90	Labels           []labels.Label
91	DiscoveredLabels []labels.Label
92	Params           url.Values
93	Reports          []*testReport
94	Active           bool
95}
96
97type testReport struct {
98	Start    time.Time
99	Duration time.Duration
100	Error    error
101}
102
103func newTestTargetRetriever(targetsInfo []*testTargetParams) *testTargetRetriever {
104	var activeTargets map[string][]*scrape.Target
105	var droppedTargets map[string][]*scrape.Target
106	activeTargets = make(map[string][]*scrape.Target)
107	droppedTargets = make(map[string][]*scrape.Target)
108
109	for _, t := range targetsInfo {
110		nt := scrape.NewTarget(t.Labels, t.DiscoveredLabels, t.Params)
111
112		for _, r := range t.Reports {
113			nt.Report(r.Start, r.Duration, r.Error)
114		}
115
116		if t.Active {
117			activeTargets[t.Identifier] = []*scrape.Target{nt}
118		} else {
119			droppedTargets[t.Identifier] = []*scrape.Target{nt}
120		}
121	}
122
123	return &testTargetRetriever{
124		activeTargets:  activeTargets,
125		droppedTargets: droppedTargets,
126	}
127}
128
129var (
130	scrapeStart = time.Now().Add(-11 * time.Second)
131)
132
133func (t testTargetRetriever) TargetsActive() map[string][]*scrape.Target {
134	return t.activeTargets
135}
136
137func (t testTargetRetriever) TargetsDropped() map[string][]*scrape.Target {
138	return t.droppedTargets
139}
140
141func (t *testTargetRetriever) SetMetadataStoreForTargets(identifier string, metadata scrape.MetricMetadataStore) error {
142	targets, ok := t.activeTargets[identifier]
143
144	if !ok {
145		return errors.New("targets not found")
146	}
147
148	for _, at := range targets {
149		at.SetMetadataStore(metadata)
150	}
151
152	return nil
153}
154
155func (t *testTargetRetriever) ResetMetadataStore() {
156	for _, at := range t.activeTargets {
157		for _, tt := range at {
158			tt.SetMetadataStore(&testMetaStore{})
159		}
160	}
161}
162
163func (t *testTargetRetriever) toFactory() func(context.Context) TargetRetriever {
164	return func(context.Context) TargetRetriever { return t }
165}
166
167type testAlertmanagerRetriever struct{}
168
169func (t testAlertmanagerRetriever) Alertmanagers() []*url.URL {
170	return []*url.URL{
171		{
172			Scheme: "http",
173			Host:   "alertmanager.example.com:8080",
174			Path:   "/api/v1/alerts",
175		},
176	}
177}
178
179func (t testAlertmanagerRetriever) DroppedAlertmanagers() []*url.URL {
180	return []*url.URL{
181		{
182			Scheme: "http",
183			Host:   "dropped.alertmanager.example.com:8080",
184			Path:   "/api/v1/alerts",
185		},
186	}
187}
188
189func (t testAlertmanagerRetriever) toFactory() func(context.Context) AlertmanagerRetriever {
190	return func(context.Context) AlertmanagerRetriever { return t }
191}
192
193type rulesRetrieverMock struct {
194	testing *testing.T
195}
196
197func (m rulesRetrieverMock) AlertingRules() []*rules.AlertingRule {
198	expr1, err := parser.ParseExpr(`absent(test_metric3) != 1`)
199	if err != nil {
200		m.testing.Fatalf("unable to parse alert expression: %s", err)
201	}
202	expr2, err := parser.ParseExpr(`up == 1`)
203	if err != nil {
204		m.testing.Fatalf("Unable to parse alert expression: %s", err)
205	}
206
207	rule1 := rules.NewAlertingRule(
208		"test_metric3",
209		expr1,
210		time.Second,
211		labels.Labels{},
212		labels.Labels{},
213		labels.Labels{},
214		"",
215		true,
216		log.NewNopLogger(),
217	)
218	rule2 := rules.NewAlertingRule(
219		"test_metric4",
220		expr2,
221		time.Second,
222		labels.Labels{},
223		labels.Labels{},
224		labels.Labels{},
225		"",
226		true,
227		log.NewNopLogger(),
228	)
229	var r []*rules.AlertingRule
230	r = append(r, rule1)
231	r = append(r, rule2)
232	return r
233}
234
235func (m rulesRetrieverMock) RuleGroups() []*rules.Group {
236	var ar rulesRetrieverMock
237	arules := ar.AlertingRules()
238	storage := teststorage.New(m.testing)
239	defer storage.Close()
240
241	engineOpts := promql.EngineOpts{
242		Logger:     nil,
243		Reg:        nil,
244		MaxSamples: 10,
245		Timeout:    100 * time.Second,
246	}
247
248	engine := promql.NewEngine(engineOpts)
249	opts := &rules.ManagerOptions{
250		QueryFunc:  rules.EngineQueryFunc(engine, storage),
251		Appendable: storage,
252		Context:    context.Background(),
253		Logger:     log.NewNopLogger(),
254	}
255
256	var r []rules.Rule
257
258	for _, alertrule := range arules {
259		r = append(r, alertrule)
260	}
261
262	recordingExpr, err := parser.ParseExpr(`vector(1)`)
263	if err != nil {
264		m.testing.Fatalf("unable to parse alert expression: %s", err)
265	}
266	recordingRule := rules.NewRecordingRule("recording-rule-1", recordingExpr, labels.Labels{})
267	r = append(r, recordingRule)
268
269	group := rules.NewGroup(rules.GroupOptions{
270		Name:          "grp",
271		File:          "/path/to/file",
272		Interval:      time.Second,
273		Rules:         r,
274		ShouldRestore: false,
275		Opts:          opts,
276	})
277	return []*rules.Group{group}
278}
279
280func (m rulesRetrieverMock) toFactory() func(context.Context) RulesRetriever {
281	return func(context.Context) RulesRetriever { return m }
282}
283
284var samplePrometheusCfg = config.Config{
285	GlobalConfig:       config.GlobalConfig{},
286	AlertingConfig:     config.AlertingConfig{},
287	RuleFiles:          []string{},
288	ScrapeConfigs:      []*config.ScrapeConfig{},
289	RemoteWriteConfigs: []*config.RemoteWriteConfig{},
290	RemoteReadConfigs:  []*config.RemoteReadConfig{},
291}
292
293var sampleFlagMap = map[string]string{
294	"flag1": "value1",
295	"flag2": "value2",
296}
297
298func TestEndpoints(t *testing.T) {
299	suite, err := promql.NewTest(t, `
300		load 1m
301			test_metric1{foo="bar"} 0+100x100
302			test_metric1{foo="boo"} 1+0x100
303			test_metric2{foo="boo"} 1+0x100
304			test_metric3{foo="bar", dup="1"} 1+0x100
305			test_metric3{foo="boo", dup="1"} 1+0x100
306			test_metric4{foo="bar", dup="1"} 1+0x100
307			test_metric4{foo="boo", dup="1"} 1+0x100
308			test_metric4{foo="boo"} 1+0x100
309	`)
310
311	start := time.Unix(0, 0)
312	exemplars := []exemplar.QueryResult{
313		{
314			SeriesLabels: labels.FromStrings("__name__", "test_metric3", "foo", "boo", "dup", "1"),
315			Exemplars: []exemplar.Exemplar{
316				{
317					Labels: labels.FromStrings("id", "abc"),
318					Value:  10,
319					Ts:     timestamp.FromTime(start.Add(2 * time.Second)),
320				},
321			},
322		},
323		{
324			SeriesLabels: labels.FromStrings("__name__", "test_metric4", "foo", "bar", "dup", "1"),
325			Exemplars: []exemplar.Exemplar{
326				{
327					Labels: labels.FromStrings("id", "lul"),
328					Value:  10,
329					Ts:     timestamp.FromTime(start.Add(4 * time.Second)),
330				},
331			},
332		},
333		{
334			SeriesLabels: labels.FromStrings("__name__", "test_metric3", "foo", "boo", "dup", "1"),
335			Exemplars: []exemplar.Exemplar{
336				{
337					Labels: labels.FromStrings("id", "abc2"),
338					Value:  10,
339					Ts:     timestamp.FromTime(start.Add(4053 * time.Millisecond)),
340				},
341			},
342		},
343		{
344			SeriesLabels: labels.FromStrings("__name__", "test_metric4", "foo", "bar", "dup", "1"),
345			Exemplars: []exemplar.Exemplar{
346				{
347					Labels: labels.FromStrings("id", "lul2"),
348					Value:  10,
349					Ts:     timestamp.FromTime(start.Add(4153 * time.Millisecond)),
350				},
351			},
352		},
353	}
354	for _, ed := range exemplars {
355		suite.ExemplarStorage().AppendExemplar(0, ed.SeriesLabels, ed.Exemplars[0])
356		require.NoError(t, err, "failed to add exemplar: %+v", ed.Exemplars[0])
357	}
358
359	require.NoError(t, err)
360	defer suite.Close()
361
362	require.NoError(t, suite.Run())
363
364	now := time.Now()
365
366	t.Run("local", func(t *testing.T) {
367		var algr rulesRetrieverMock
368		algr.testing = t
369
370		algr.AlertingRules()
371
372		algr.RuleGroups()
373
374		testTargetRetriever := setupTestTargetRetriever(t)
375
376		api := &API{
377			Queryable:             suite.Storage(),
378			QueryEngine:           suite.QueryEngine(),
379			ExemplarQueryable:     suite.ExemplarQueryable(),
380			targetRetriever:       testTargetRetriever.toFactory(),
381			alertmanagerRetriever: testAlertmanagerRetriever{}.toFactory(),
382			flagsMap:              sampleFlagMap,
383			now:                   func() time.Time { return now },
384			config:                func() config.Config { return samplePrometheusCfg },
385			ready:                 func(f http.HandlerFunc) http.HandlerFunc { return f },
386			rulesRetriever:        algr.toFactory(),
387		}
388		testEndpoints(t, api, testTargetRetriever, suite.ExemplarStorage(), true)
389	})
390
391	// Run all the API tests against a API that is wired to forward queries via
392	// the remote read client to a test server, which in turn sends them to the
393	// data from the test suite.
394	t.Run("remote", func(t *testing.T) {
395		server := setupRemote(suite.Storage())
396		defer server.Close()
397
398		u, err := url.Parse(server.URL)
399		require.NoError(t, err)
400
401		al := promlog.AllowedLevel{}
402		require.NoError(t, al.Set("debug"))
403
404		af := promlog.AllowedFormat{}
405		require.NoError(t, af.Set("logfmt"))
406
407		promlogConfig := promlog.Config{
408			Level:  &al,
409			Format: &af,
410		}
411
412		dbDir, err := ioutil.TempDir("", "tsdb-api-ready")
413		require.NoError(t, err)
414		defer os.RemoveAll(dbDir)
415
416		remote := remote.NewStorage(promlog.New(&promlogConfig), prometheus.DefaultRegisterer, func() (int64, error) {
417			return 0, nil
418		}, dbDir, 1*time.Second, nil)
419
420		err = remote.ApplyConfig(&config.Config{
421			RemoteReadConfigs: []*config.RemoteReadConfig{
422				{
423					URL:           &config_util.URL{URL: u},
424					RemoteTimeout: model.Duration(1 * time.Second),
425					ReadRecent:    true,
426				},
427			},
428		})
429		require.NoError(t, err)
430
431		var algr rulesRetrieverMock
432		algr.testing = t
433
434		algr.AlertingRules()
435
436		algr.RuleGroups()
437
438		testTargetRetriever := setupTestTargetRetriever(t)
439
440		api := &API{
441			Queryable:             remote,
442			QueryEngine:           suite.QueryEngine(),
443			ExemplarQueryable:     suite.ExemplarQueryable(),
444			targetRetriever:       testTargetRetriever.toFactory(),
445			alertmanagerRetriever: testAlertmanagerRetriever{}.toFactory(),
446			flagsMap:              sampleFlagMap,
447			now:                   func() time.Time { return now },
448			config:                func() config.Config { return samplePrometheusCfg },
449			ready:                 func(f http.HandlerFunc) http.HandlerFunc { return f },
450			rulesRetriever:        algr.toFactory(),
451		}
452
453		testEndpoints(t, api, testTargetRetriever, suite.ExemplarStorage(), false)
454	})
455
456}
457
458func TestLabelNames(t *testing.T) {
459	// TestEndpoints doesn't have enough label names to test api.labelNames
460	// endpoint properly. Hence we test it separately.
461	suite, err := promql.NewTest(t, `
462		load 1m
463			test_metric1{foo1="bar", baz="abc"} 0+100x100
464			test_metric1{foo2="boo"} 1+0x100
465			test_metric2{foo="boo"} 1+0x100
466			test_metric2{foo="boo", xyz="qwerty"} 1+0x100
467			test_metric2{foo="baz", abc="qwerty"} 1+0x100
468	`)
469	require.NoError(t, err)
470	defer suite.Close()
471	require.NoError(t, suite.Run())
472
473	api := &API{
474		Queryable: suite.Storage(),
475	}
476	request := func(method string, matchers ...string) (*http.Request, error) {
477		u, err := url.Parse("http://example.com")
478		require.NoError(t, err)
479		q := u.Query()
480		for _, matcher := range matchers {
481			q.Add("match[]", matcher)
482		}
483		u.RawQuery = q.Encode()
484
485		r, err := http.NewRequest(method, u.String(), nil)
486		if method == http.MethodPost {
487			r.Header.Set("Content-Type", "application/x-www-form-urlencoded")
488		}
489		return r, err
490	}
491
492	for _, tc := range []struct {
493		name     string
494		matchers []string
495		expected []string
496	}{
497		{
498			name:     "no matchers",
499			expected: []string{"__name__", "abc", "baz", "foo", "foo1", "foo2", "xyz"},
500		},
501		{
502			name:     "non empty label matcher",
503			matchers: []string{`{foo=~".+"}`},
504			expected: []string{"__name__", "abc", "foo", "xyz"},
505		},
506		{
507			name:     "exact label matcher",
508			matchers: []string{`{foo="boo"}`},
509			expected: []string{"__name__", "foo", "xyz"},
510		},
511		{
512			name:     "two matchers",
513			matchers: []string{`{foo="boo"}`, `{foo="baz"}`},
514			expected: []string{"__name__", "abc", "foo", "xyz"},
515		},
516	} {
517		t.Run(tc.name, func(t *testing.T) {
518			for _, method := range []string{http.MethodGet, http.MethodPost} {
519				ctx := context.Background()
520				req, err := request(method, tc.matchers...)
521				require.NoError(t, err)
522				res := api.labelNames(req.WithContext(ctx))
523				assertAPIError(t, res.err, "")
524				assertAPIResponse(t, res.data, tc.expected)
525			}
526		})
527	}
528}
529
530func setupTestTargetRetriever(t *testing.T) *testTargetRetriever {
531	t.Helper()
532
533	targets := []*testTargetParams{
534		{
535			Identifier: "test",
536			Labels: labels.FromMap(map[string]string{
537				model.SchemeLabel:         "http",
538				model.AddressLabel:        "example.com:8080",
539				model.MetricsPathLabel:    "/metrics",
540				model.JobLabel:            "test",
541				model.ScrapeIntervalLabel: "15s",
542				model.ScrapeTimeoutLabel:  "5s",
543			}),
544			DiscoveredLabels: nil,
545			Params:           url.Values{},
546			Reports:          []*testReport{{scrapeStart, 70 * time.Millisecond, nil}},
547			Active:           true,
548		},
549		{
550			Identifier: "blackbox",
551			Labels: labels.FromMap(map[string]string{
552				model.SchemeLabel:         "http",
553				model.AddressLabel:        "localhost:9115",
554				model.MetricsPathLabel:    "/probe",
555				model.JobLabel:            "blackbox",
556				model.ScrapeIntervalLabel: "20s",
557				model.ScrapeTimeoutLabel:  "10s",
558			}),
559			DiscoveredLabels: nil,
560			Params:           url.Values{"target": []string{"example.com"}},
561			Reports:          []*testReport{{scrapeStart, 100 * time.Millisecond, errors.New("failed")}},
562			Active:           true,
563		},
564		{
565			Identifier: "blackbox",
566			Labels:     nil,
567			DiscoveredLabels: labels.FromMap(map[string]string{
568				model.SchemeLabel:         "http",
569				model.AddressLabel:        "http://dropped.example.com:9115",
570				model.MetricsPathLabel:    "/probe",
571				model.JobLabel:            "blackbox",
572				model.ScrapeIntervalLabel: "30s",
573				model.ScrapeTimeoutLabel:  "15s",
574			}),
575			Params: url.Values{},
576			Active: false,
577		},
578	}
579
580	return newTestTargetRetriever(targets)
581}
582
583func setupRemote(s storage.Storage) *httptest.Server {
584	handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
585		req, err := remote.DecodeReadRequest(r)
586		if err != nil {
587			http.Error(w, err.Error(), http.StatusBadRequest)
588			return
589		}
590		resp := prompb.ReadResponse{
591			Results: make([]*prompb.QueryResult, len(req.Queries)),
592		}
593		for i, query := range req.Queries {
594			matchers, err := remote.FromLabelMatchers(query.Matchers)
595			if err != nil {
596				http.Error(w, err.Error(), http.StatusBadRequest)
597				return
598			}
599
600			var hints *storage.SelectHints
601			if query.Hints != nil {
602				hints = &storage.SelectHints{
603					Start: query.Hints.StartMs,
604					End:   query.Hints.EndMs,
605					Step:  query.Hints.StepMs,
606					Func:  query.Hints.Func,
607				}
608			}
609
610			querier, err := s.Querier(r.Context(), query.StartTimestampMs, query.EndTimestampMs)
611			if err != nil {
612				http.Error(w, err.Error(), http.StatusInternalServerError)
613				return
614			}
615			defer querier.Close()
616
617			set := querier.Select(false, hints, matchers...)
618			resp.Results[i], _, err = remote.ToQueryResult(set, 1e6)
619			if err != nil {
620				http.Error(w, err.Error(), http.StatusInternalServerError)
621				return
622			}
623		}
624
625		if err := remote.EncodeReadResponse(&resp, w); err != nil {
626			http.Error(w, err.Error(), http.StatusInternalServerError)
627			return
628		}
629	})
630
631	return httptest.NewServer(handler)
632}
633
634func testEndpoints(t *testing.T, api *API, tr *testTargetRetriever, es storage.ExemplarStorage, testLabelAPI bool) {
635	start := time.Unix(0, 0)
636
637	type targetMetadata struct {
638		identifier string
639		metadata   []scrape.MetricMetadata
640	}
641
642	type test struct {
643		endpoint    apiFunc
644		params      map[string]string
645		query       url.Values
646		response    interface{}
647		responseLen int
648		errType     errorType
649		sorter      func(interface{})
650		metadata    []targetMetadata
651		exemplars   []exemplar.QueryResult
652	}
653
654	var tests = []test{
655		{
656			endpoint: api.query,
657			query: url.Values{
658				"query": []string{"2"},
659				"time":  []string{"123.4"},
660			},
661			response: &queryData{
662				ResultType: parser.ValueTypeScalar,
663				Result: promql.Scalar{
664					V: 2,
665					T: timestamp.FromTime(start.Add(123*time.Second + 400*time.Millisecond)),
666				},
667			},
668		},
669		{
670			endpoint: api.query,
671			query: url.Values{
672				"query": []string{"0.333"},
673				"time":  []string{"1970-01-01T00:02:03Z"},
674			},
675			response: &queryData{
676				ResultType: parser.ValueTypeScalar,
677				Result: promql.Scalar{
678					V: 0.333,
679					T: timestamp.FromTime(start.Add(123 * time.Second)),
680				},
681			},
682		},
683		{
684			endpoint: api.query,
685			query: url.Values{
686				"query": []string{"0.333"},
687				"time":  []string{"1970-01-01T01:02:03+01:00"},
688			},
689			response: &queryData{
690				ResultType: parser.ValueTypeScalar,
691				Result: promql.Scalar{
692					V: 0.333,
693					T: timestamp.FromTime(start.Add(123 * time.Second)),
694				},
695			},
696		},
697		{
698			endpoint: api.query,
699			query: url.Values{
700				"query": []string{"0.333"},
701			},
702			response: &queryData{
703				ResultType: parser.ValueTypeScalar,
704				Result: promql.Scalar{
705					V: 0.333,
706					T: timestamp.FromTime(api.now()),
707				},
708			},
709		},
710		{
711			endpoint: api.queryRange,
712			query: url.Values{
713				"query": []string{"time()"},
714				"start": []string{"0"},
715				"end":   []string{"2"},
716				"step":  []string{"1"},
717			},
718			response: &queryData{
719				ResultType: parser.ValueTypeMatrix,
720				Result: promql.Matrix{
721					promql.Series{
722						Points: []promql.Point{
723							{V: 0, T: timestamp.FromTime(start)},
724							{V: 1, T: timestamp.FromTime(start.Add(1 * time.Second))},
725							{V: 2, T: timestamp.FromTime(start.Add(2 * time.Second))},
726						},
727						Metric: nil,
728					},
729				},
730			},
731		},
732		// Missing query params in range queries.
733		{
734			endpoint: api.queryRange,
735			query: url.Values{
736				"query": []string{"time()"},
737				"end":   []string{"2"},
738				"step":  []string{"1"},
739			},
740			errType: errorBadData,
741		},
742		{
743			endpoint: api.queryRange,
744			query: url.Values{
745				"query": []string{"time()"},
746				"start": []string{"0"},
747				"step":  []string{"1"},
748			},
749			errType: errorBadData,
750		},
751		{
752			endpoint: api.queryRange,
753			query: url.Values{
754				"query": []string{"time()"},
755				"start": []string{"0"},
756				"end":   []string{"2"},
757			},
758			errType: errorBadData,
759		},
760		// Bad query expression.
761		{
762			endpoint: api.query,
763			query: url.Values{
764				"query": []string{"invalid][query"},
765				"time":  []string{"1970-01-01T01:02:03+01:00"},
766			},
767			errType: errorBadData,
768		},
769		{
770			endpoint: api.queryRange,
771			query: url.Values{
772				"query": []string{"invalid][query"},
773				"start": []string{"0"},
774				"end":   []string{"100"},
775				"step":  []string{"1"},
776			},
777			errType: errorBadData,
778		},
779		// Invalid step.
780		{
781			endpoint: api.queryRange,
782			query: url.Values{
783				"query": []string{"time()"},
784				"start": []string{"1"},
785				"end":   []string{"2"},
786				"step":  []string{"0"},
787			},
788			errType: errorBadData,
789		},
790		// Start after end.
791		{
792			endpoint: api.queryRange,
793			query: url.Values{
794				"query": []string{"time()"},
795				"start": []string{"2"},
796				"end":   []string{"1"},
797				"step":  []string{"1"},
798			},
799			errType: errorBadData,
800		},
801		// Start overflows int64 internally.
802		{
803			endpoint: api.queryRange,
804			query: url.Values{
805				"query": []string{"time()"},
806				"start": []string{"148966367200.372"},
807				"end":   []string{"1489667272.372"},
808				"step":  []string{"1"},
809			},
810			errType: errorBadData,
811		},
812		{
813			endpoint: api.series,
814			query: url.Values{
815				"match[]": []string{`test_metric2`},
816			},
817			response: []labels.Labels{
818				labels.FromStrings("__name__", "test_metric2", "foo", "boo"),
819			},
820		},
821		{
822			endpoint: api.series,
823			query: url.Values{
824				"match[]": []string{`{foo=""}`},
825			},
826			errType: errorBadData,
827		},
828		{
829			endpoint: api.series,
830			query: url.Values{
831				"match[]": []string{`test_metric1{foo=~".+o"}`},
832			},
833			response: []labels.Labels{
834				labels.FromStrings("__name__", "test_metric1", "foo", "boo"),
835			},
836		},
837		{
838			endpoint: api.series,
839			query: url.Values{
840				"match[]": []string{`test_metric1{foo=~".+o$"}`, `test_metric1{foo=~".+o"}`},
841			},
842			response: []labels.Labels{
843				labels.FromStrings("__name__", "test_metric1", "foo", "boo"),
844			},
845		},
846		// Try to overlap the selected series set as much as possible to test the result de-duplication works well.
847		{
848			endpoint: api.series,
849			query: url.Values{
850				"match[]": []string{`test_metric4{foo=~".+o$"}`, `test_metric4{dup=~"^1"}`},
851			},
852			response: []labels.Labels{
853				labels.FromStrings("__name__", "test_metric4", "dup", "1", "foo", "bar"),
854				labels.FromStrings("__name__", "test_metric4", "dup", "1", "foo", "boo"),
855				labels.FromStrings("__name__", "test_metric4", "foo", "boo"),
856			},
857		},
858		{
859			endpoint: api.series,
860			query: url.Values{
861				"match[]": []string{`test_metric1{foo=~".+o"}`, `none`},
862			},
863			response: []labels.Labels{
864				labels.FromStrings("__name__", "test_metric1", "foo", "boo"),
865			},
866		},
867		// Start and end before series starts.
868		{
869			endpoint: api.series,
870			query: url.Values{
871				"match[]": []string{`test_metric2`},
872				"start":   []string{"-2"},
873				"end":     []string{"-1"},
874			},
875			response: []labels.Labels{},
876		},
877		// Start and end after series ends.
878		{
879			endpoint: api.series,
880			query: url.Values{
881				"match[]": []string{`test_metric2`},
882				"start":   []string{"100000"},
883				"end":     []string{"100001"},
884			},
885			response: []labels.Labels{},
886		},
887		// Start before series starts, end after series ends.
888		{
889			endpoint: api.series,
890			query: url.Values{
891				"match[]": []string{`test_metric2`},
892				"start":   []string{"-1"},
893				"end":     []string{"100000"},
894			},
895			response: []labels.Labels{
896				labels.FromStrings("__name__", "test_metric2", "foo", "boo"),
897			},
898		},
899		// Start and end within series.
900		{
901			endpoint: api.series,
902			query: url.Values{
903				"match[]": []string{`test_metric2`},
904				"start":   []string{"1"},
905				"end":     []string{"100"},
906			},
907			response: []labels.Labels{
908				labels.FromStrings("__name__", "test_metric2", "foo", "boo"),
909			},
910		},
911		// Start within series, end after.
912		{
913			endpoint: api.series,
914			query: url.Values{
915				"match[]": []string{`test_metric2`},
916				"start":   []string{"1"},
917				"end":     []string{"100000"},
918			},
919			response: []labels.Labels{
920				labels.FromStrings("__name__", "test_metric2", "foo", "boo"),
921			},
922		},
923		// Start before series, end within series.
924		{
925			endpoint: api.series,
926			query: url.Values{
927				"match[]": []string{`test_metric2`},
928				"start":   []string{"-1"},
929				"end":     []string{"1"},
930			},
931			response: []labels.Labels{
932				labels.FromStrings("__name__", "test_metric2", "foo", "boo"),
933			},
934		},
935		// Missing match[] query params in series requests.
936		{
937			endpoint: api.series,
938			errType:  errorBadData,
939		},
940		{
941			endpoint: api.dropSeries,
942			errType:  errorInternal,
943		},
944		{
945			endpoint: api.targets,
946			response: &TargetDiscovery{
947				ActiveTargets: []*Target{
948					{
949						DiscoveredLabels: map[string]string{},
950						Labels: map[string]string{
951							"job": "blackbox",
952						},
953						ScrapePool:         "blackbox",
954						ScrapeURL:          "http://localhost:9115/probe?target=example.com",
955						GlobalURL:          "http://localhost:9115/probe?target=example.com",
956						Health:             "down",
957						LastError:          "failed: missing port in address",
958						LastScrape:         scrapeStart,
959						LastScrapeDuration: 0.1,
960						ScrapeInterval:     "20s",
961						ScrapeTimeout:      "10s",
962					},
963					{
964						DiscoveredLabels: map[string]string{},
965						Labels: map[string]string{
966							"job": "test",
967						},
968						ScrapePool:         "test",
969						ScrapeURL:          "http://example.com:8080/metrics",
970						GlobalURL:          "http://example.com:8080/metrics",
971						Health:             "up",
972						LastError:          "",
973						LastScrape:         scrapeStart,
974						LastScrapeDuration: 0.07,
975						ScrapeInterval:     "15s",
976						ScrapeTimeout:      "5s",
977					},
978				},
979				DroppedTargets: []*DroppedTarget{
980					{
981						DiscoveredLabels: map[string]string{
982							"__address__":         "http://dropped.example.com:9115",
983							"__metrics_path__":    "/probe",
984							"__scheme__":          "http",
985							"job":                 "blackbox",
986							"__scrape_interval__": "30s",
987							"__scrape_timeout__":  "15s",
988						},
989					},
990				},
991			},
992		},
993		{
994			endpoint: api.targets,
995			query: url.Values{
996				"state": []string{"any"},
997			},
998			response: &TargetDiscovery{
999				ActiveTargets: []*Target{
1000					{
1001						DiscoveredLabels: map[string]string{},
1002						Labels: map[string]string{
1003							"job": "blackbox",
1004						},
1005						ScrapePool:         "blackbox",
1006						ScrapeURL:          "http://localhost:9115/probe?target=example.com",
1007						GlobalURL:          "http://localhost:9115/probe?target=example.com",
1008						Health:             "down",
1009						LastError:          "failed: missing port in address",
1010						LastScrape:         scrapeStart,
1011						LastScrapeDuration: 0.1,
1012						ScrapeInterval:     "20s",
1013						ScrapeTimeout:      "10s",
1014					},
1015					{
1016						DiscoveredLabels: map[string]string{},
1017						Labels: map[string]string{
1018							"job": "test",
1019						},
1020						ScrapePool:         "test",
1021						ScrapeURL:          "http://example.com:8080/metrics",
1022						GlobalURL:          "http://example.com:8080/metrics",
1023						Health:             "up",
1024						LastError:          "",
1025						LastScrape:         scrapeStart,
1026						LastScrapeDuration: 0.07,
1027						ScrapeInterval:     "15s",
1028						ScrapeTimeout:      "5s",
1029					},
1030				},
1031				DroppedTargets: []*DroppedTarget{
1032					{
1033						DiscoveredLabels: map[string]string{
1034							"__address__":         "http://dropped.example.com:9115",
1035							"__metrics_path__":    "/probe",
1036							"__scheme__":          "http",
1037							"job":                 "blackbox",
1038							"__scrape_interval__": "30s",
1039							"__scrape_timeout__":  "15s",
1040						},
1041					},
1042				},
1043			},
1044		},
1045		{
1046			endpoint: api.targets,
1047			query: url.Values{
1048				"state": []string{"active"},
1049			},
1050			response: &TargetDiscovery{
1051				ActiveTargets: []*Target{
1052					{
1053						DiscoveredLabels: map[string]string{},
1054						Labels: map[string]string{
1055							"job": "blackbox",
1056						},
1057						ScrapePool:         "blackbox",
1058						ScrapeURL:          "http://localhost:9115/probe?target=example.com",
1059						GlobalURL:          "http://localhost:9115/probe?target=example.com",
1060						Health:             "down",
1061						LastError:          "failed: missing port in address",
1062						LastScrape:         scrapeStart,
1063						LastScrapeDuration: 0.1,
1064						ScrapeInterval:     "20s",
1065						ScrapeTimeout:      "10s",
1066					},
1067					{
1068						DiscoveredLabels: map[string]string{},
1069						Labels: map[string]string{
1070							"job": "test",
1071						},
1072						ScrapePool:         "test",
1073						ScrapeURL:          "http://example.com:8080/metrics",
1074						GlobalURL:          "http://example.com:8080/metrics",
1075						Health:             "up",
1076						LastError:          "",
1077						LastScrape:         scrapeStart,
1078						LastScrapeDuration: 0.07,
1079						ScrapeInterval:     "15s",
1080						ScrapeTimeout:      "5s",
1081					},
1082				},
1083				DroppedTargets: []*DroppedTarget{},
1084			},
1085		},
1086		{
1087			endpoint: api.targets,
1088			query: url.Values{
1089				"state": []string{"Dropped"},
1090			},
1091			response: &TargetDiscovery{
1092				ActiveTargets: []*Target{},
1093				DroppedTargets: []*DroppedTarget{
1094					{
1095						DiscoveredLabels: map[string]string{
1096							"__address__":         "http://dropped.example.com:9115",
1097							"__metrics_path__":    "/probe",
1098							"__scheme__":          "http",
1099							"job":                 "blackbox",
1100							"__scrape_interval__": "30s",
1101							"__scrape_timeout__":  "15s",
1102						},
1103					},
1104				},
1105			},
1106		},
1107		// With a matching metric.
1108		{
1109			endpoint: api.targetMetadata,
1110			query: url.Values{
1111				"metric": []string{"go_threads"},
1112			},
1113			metadata: []targetMetadata{
1114				{
1115					identifier: "test",
1116					metadata: []scrape.MetricMetadata{
1117						{
1118							Metric: "go_threads",
1119							Type:   textparse.MetricTypeGauge,
1120							Help:   "Number of OS threads created.",
1121							Unit:   "",
1122						},
1123					},
1124				},
1125			},
1126			response: []metricMetadata{
1127				{
1128					Target: labels.FromMap(map[string]string{
1129						"job": "test",
1130					}),
1131					Help: "Number of OS threads created.",
1132					Type: textparse.MetricTypeGauge,
1133					Unit: "",
1134				},
1135			},
1136		},
1137		// With a matching target.
1138		{
1139			endpoint: api.targetMetadata,
1140			query: url.Values{
1141				"match_target": []string{"{job=\"blackbox\"}"},
1142			},
1143			metadata: []targetMetadata{
1144				{
1145					identifier: "blackbox",
1146					metadata: []scrape.MetricMetadata{
1147						{
1148							Metric: "prometheus_tsdb_storage_blocks_bytes",
1149							Type:   textparse.MetricTypeGauge,
1150							Help:   "The number of bytes that are currently used for local storage by all blocks.",
1151							Unit:   "",
1152						},
1153					},
1154				},
1155			},
1156			response: []metricMetadata{
1157				{
1158					Target: labels.FromMap(map[string]string{
1159						"job": "blackbox",
1160					}),
1161					Metric: "prometheus_tsdb_storage_blocks_bytes",
1162					Help:   "The number of bytes that are currently used for local storage by all blocks.",
1163					Type:   textparse.MetricTypeGauge,
1164					Unit:   "",
1165				},
1166			},
1167		},
1168		// Without a target or metric.
1169		{
1170			endpoint: api.targetMetadata,
1171			metadata: []targetMetadata{
1172				{
1173					identifier: "test",
1174					metadata: []scrape.MetricMetadata{
1175						{
1176							Metric: "go_threads",
1177							Type:   textparse.MetricTypeGauge,
1178							Help:   "Number of OS threads created.",
1179							Unit:   "",
1180						},
1181					},
1182				},
1183				{
1184					identifier: "blackbox",
1185					metadata: []scrape.MetricMetadata{
1186						{
1187							Metric: "prometheus_tsdb_storage_blocks_bytes",
1188							Type:   textparse.MetricTypeGauge,
1189							Help:   "The number of bytes that are currently used for local storage by all blocks.",
1190							Unit:   "",
1191						},
1192					},
1193				},
1194			},
1195			response: []metricMetadata{
1196				{
1197					Target: labels.FromMap(map[string]string{
1198						"job": "test",
1199					}),
1200					Metric: "go_threads",
1201					Help:   "Number of OS threads created.",
1202					Type:   textparse.MetricTypeGauge,
1203					Unit:   "",
1204				},
1205				{
1206					Target: labels.FromMap(map[string]string{
1207						"job": "blackbox",
1208					}),
1209					Metric: "prometheus_tsdb_storage_blocks_bytes",
1210					Help:   "The number of bytes that are currently used for local storage by all blocks.",
1211					Type:   textparse.MetricTypeGauge,
1212					Unit:   "",
1213				},
1214			},
1215			sorter: func(m interface{}) {
1216				sort.Slice(m.([]metricMetadata), func(i, j int) bool {
1217					s := m.([]metricMetadata)
1218					return s[i].Metric < s[j].Metric
1219				})
1220			},
1221		},
1222		// Without a matching metric.
1223		{
1224			endpoint: api.targetMetadata,
1225			query: url.Values{
1226				"match_target": []string{"{job=\"non-existentblackbox\"}"},
1227			},
1228			response: []metricMetadata{},
1229		},
1230		{
1231			endpoint: api.alertmanagers,
1232			response: &AlertmanagerDiscovery{
1233				ActiveAlertmanagers: []*AlertmanagerTarget{
1234					{
1235						URL: "http://alertmanager.example.com:8080/api/v1/alerts",
1236					},
1237				},
1238				DroppedAlertmanagers: []*AlertmanagerTarget{
1239					{
1240						URL: "http://dropped.alertmanager.example.com:8080/api/v1/alerts",
1241					},
1242				},
1243			},
1244		},
1245		// With metadata available.
1246		{
1247			endpoint: api.metricMetadata,
1248			metadata: []targetMetadata{
1249				{
1250					identifier: "test",
1251					metadata: []scrape.MetricMetadata{
1252						{
1253							Metric: "prometheus_engine_query_duration_seconds",
1254							Type:   textparse.MetricTypeSummary,
1255							Help:   "Query timings",
1256							Unit:   "",
1257						},
1258						{
1259							Metric: "go_info",
1260							Type:   textparse.MetricTypeGauge,
1261							Help:   "Information about the Go environment.",
1262							Unit:   "",
1263						},
1264					},
1265				},
1266			},
1267			response: map[string][]metadata{
1268				"prometheus_engine_query_duration_seconds": {{textparse.MetricTypeSummary, "Query timings", ""}},
1269				"go_info": {{textparse.MetricTypeGauge, "Information about the Go environment.", ""}},
1270			},
1271		},
1272		// With duplicate metadata for a metric that comes from different targets.
1273		{
1274			endpoint: api.metricMetadata,
1275			metadata: []targetMetadata{
1276				{
1277					identifier: "test",
1278					metadata: []scrape.MetricMetadata{
1279						{
1280							Metric: "go_threads",
1281							Type:   textparse.MetricTypeGauge,
1282							Help:   "Number of OS threads created",
1283							Unit:   "",
1284						},
1285					},
1286				},
1287				{
1288					identifier: "blackbox",
1289					metadata: []scrape.MetricMetadata{
1290						{
1291							Metric: "go_threads",
1292							Type:   textparse.MetricTypeGauge,
1293							Help:   "Number of OS threads created",
1294							Unit:   "",
1295						},
1296					},
1297				},
1298			},
1299			response: map[string][]metadata{
1300				"go_threads": {{textparse.MetricTypeGauge, "Number of OS threads created", ""}},
1301			},
1302		},
1303		// With non-duplicate metadata for the same metric from different targets.
1304		{
1305			endpoint: api.metricMetadata,
1306			metadata: []targetMetadata{
1307				{
1308					identifier: "test",
1309					metadata: []scrape.MetricMetadata{
1310						{
1311							Metric: "go_threads",
1312							Type:   textparse.MetricTypeGauge,
1313							Help:   "Number of OS threads created",
1314							Unit:   "",
1315						},
1316					},
1317				},
1318				{
1319					identifier: "blackbox",
1320					metadata: []scrape.MetricMetadata{
1321						{
1322							Metric: "go_threads",
1323							Type:   textparse.MetricTypeGauge,
1324							Help:   "Number of OS threads that were created.",
1325							Unit:   "",
1326						},
1327					},
1328				},
1329			},
1330			response: map[string][]metadata{
1331				"go_threads": {
1332					{textparse.MetricTypeGauge, "Number of OS threads created", ""},
1333					{textparse.MetricTypeGauge, "Number of OS threads that were created.", ""},
1334				},
1335			},
1336			sorter: func(m interface{}) {
1337				v := m.(map[string][]metadata)["go_threads"]
1338
1339				sort.Slice(v, func(i, j int) bool {
1340					return v[i].Help < v[j].Help
1341				})
1342			},
1343		},
1344		// With a limit for the number of metrics returned.
1345		{
1346			endpoint: api.metricMetadata,
1347			query: url.Values{
1348				"limit": []string{"2"},
1349			},
1350			metadata: []targetMetadata{
1351				{
1352					identifier: "test",
1353					metadata: []scrape.MetricMetadata{
1354						{
1355							Metric: "go_threads",
1356							Type:   textparse.MetricTypeGauge,
1357							Help:   "Number of OS threads created",
1358							Unit:   "",
1359						},
1360						{
1361							Metric: "prometheus_engine_query_duration_seconds",
1362							Type:   textparse.MetricTypeSummary,
1363							Help:   "Query Timmings.",
1364							Unit:   "",
1365						},
1366					},
1367				},
1368				{
1369					identifier: "blackbox",
1370					metadata: []scrape.MetricMetadata{
1371						{
1372							Metric: "go_gc_duration_seconds",
1373							Type:   textparse.MetricTypeSummary,
1374							Help:   "A summary of the GC invocation durations.",
1375							Unit:   "",
1376						},
1377					},
1378				},
1379			},
1380			responseLen: 2,
1381		},
1382		// When requesting a specific metric that is present.
1383		{
1384			endpoint: api.metricMetadata,
1385			query:    url.Values{"metric": []string{"go_threads"}},
1386			metadata: []targetMetadata{
1387				{
1388					identifier: "test",
1389					metadata: []scrape.MetricMetadata{
1390						{
1391							Metric: "go_threads",
1392							Type:   textparse.MetricTypeGauge,
1393							Help:   "Number of OS threads created",
1394							Unit:   "",
1395						},
1396					},
1397				},
1398				{
1399					identifier: "blackbox",
1400					metadata: []scrape.MetricMetadata{
1401						{
1402							Metric: "go_gc_duration_seconds",
1403							Type:   textparse.MetricTypeSummary,
1404							Help:   "A summary of the GC invocation durations.",
1405							Unit:   "",
1406						},
1407						{
1408							Metric: "go_threads",
1409							Type:   textparse.MetricTypeGauge,
1410							Help:   "Number of OS threads that were created.",
1411							Unit:   "",
1412						},
1413					},
1414				},
1415			},
1416			response: map[string][]metadata{
1417				"go_threads": {
1418					{textparse.MetricTypeGauge, "Number of OS threads created", ""},
1419					{textparse.MetricTypeGauge, "Number of OS threads that were created.", ""},
1420				},
1421			},
1422			sorter: func(m interface{}) {
1423				v := m.(map[string][]metadata)["go_threads"]
1424
1425				sort.Slice(v, func(i, j int) bool {
1426					return v[i].Help < v[j].Help
1427				})
1428			},
1429		},
1430		// With a specific metric that is not present.
1431		{
1432			endpoint: api.metricMetadata,
1433			query:    url.Values{"metric": []string{"go_gc_duration_seconds"}},
1434			metadata: []targetMetadata{
1435				{
1436					identifier: "test",
1437					metadata: []scrape.MetricMetadata{
1438						{
1439							Metric: "go_threads",
1440							Type:   textparse.MetricTypeGauge,
1441							Help:   "Number of OS threads created",
1442							Unit:   "",
1443						},
1444					},
1445				},
1446			},
1447			response: map[string][]metadata{},
1448		},
1449		// With no available metadata.
1450		{
1451			endpoint: api.metricMetadata,
1452			response: map[string][]metadata{},
1453		},
1454		{
1455			endpoint: api.serveConfig,
1456			response: &prometheusConfig{
1457				YAML: samplePrometheusCfg.String(),
1458			},
1459		},
1460		{
1461			endpoint: api.serveFlags,
1462			response: sampleFlagMap,
1463		},
1464		{
1465			endpoint: api.alerts,
1466			response: &AlertDiscovery{
1467				Alerts: []*Alert{},
1468			},
1469		},
1470		{
1471			endpoint: api.rules,
1472			response: &RuleDiscovery{
1473				RuleGroups: []*RuleGroup{
1474					{
1475						Name:     "grp",
1476						File:     "/path/to/file",
1477						Interval: 1,
1478						Rules: []rule{
1479							alertingRule{
1480								State:       "inactive",
1481								Name:        "test_metric3",
1482								Query:       "absent(test_metric3) != 1",
1483								Duration:    1,
1484								Labels:      labels.Labels{},
1485								Annotations: labels.Labels{},
1486								Alerts:      []*Alert{},
1487								Health:      "unknown",
1488								Type:        "alerting",
1489							},
1490							alertingRule{
1491								State:       "inactive",
1492								Name:        "test_metric4",
1493								Query:       "up == 1",
1494								Duration:    1,
1495								Labels:      labels.Labels{},
1496								Annotations: labels.Labels{},
1497								Alerts:      []*Alert{},
1498								Health:      "unknown",
1499								Type:        "alerting",
1500							},
1501							recordingRule{
1502								Name:   "recording-rule-1",
1503								Query:  "vector(1)",
1504								Labels: labels.Labels{},
1505								Health: "unknown",
1506								Type:   "recording",
1507							},
1508						},
1509					},
1510				},
1511			},
1512		},
1513		{
1514			endpoint: api.rules,
1515			query: url.Values{
1516				"type": []string{"alert"},
1517			},
1518			response: &RuleDiscovery{
1519				RuleGroups: []*RuleGroup{
1520					{
1521						Name:     "grp",
1522						File:     "/path/to/file",
1523						Interval: 1,
1524						Rules: []rule{
1525							alertingRule{
1526								State:       "inactive",
1527								Name:        "test_metric3",
1528								Query:       "absent(test_metric3) != 1",
1529								Duration:    1,
1530								Labels:      labels.Labels{},
1531								Annotations: labels.Labels{},
1532								Alerts:      []*Alert{},
1533								Health:      "unknown",
1534								Type:        "alerting",
1535							},
1536							alertingRule{
1537								State:       "inactive",
1538								Name:        "test_metric4",
1539								Query:       "up == 1",
1540								Duration:    1,
1541								Labels:      labels.Labels{},
1542								Annotations: labels.Labels{},
1543								Alerts:      []*Alert{},
1544								Health:      "unknown",
1545								Type:        "alerting",
1546							},
1547						},
1548					},
1549				},
1550			},
1551		},
1552		{
1553			endpoint: api.rules,
1554			query: url.Values{
1555				"type": []string{"record"},
1556			},
1557			response: &RuleDiscovery{
1558				RuleGroups: []*RuleGroup{
1559					{
1560						Name:     "grp",
1561						File:     "/path/to/file",
1562						Interval: 1,
1563						Rules: []rule{
1564							recordingRule{
1565								Name:   "recording-rule-1",
1566								Query:  "vector(1)",
1567								Labels: labels.Labels{},
1568								Health: "unknown",
1569								Type:   "recording",
1570							},
1571						},
1572					},
1573				},
1574			},
1575		},
1576		{
1577			endpoint: api.queryExemplars,
1578			query: url.Values{
1579				"query": []string{`test_metric3{foo="boo"} - test_metric4{foo="bar"}`},
1580				"start": []string{"0"},
1581				"end":   []string{"4"},
1582			},
1583			// Note extra integer length of timestamps for exemplars because of millisecond preservation
1584			// of timestamps within Prometheus (see timestamp package).
1585
1586			response: []exemplar.QueryResult{
1587				{
1588					SeriesLabels: labels.FromStrings("__name__", "test_metric3", "foo", "boo", "dup", "1"),
1589					Exemplars: []exemplar.Exemplar{
1590						{
1591							Labels: labels.FromStrings("id", "abc"),
1592							Value:  10,
1593							Ts:     timestamp.FromTime(start.Add(2 * time.Second)),
1594						},
1595					},
1596				},
1597				{
1598					SeriesLabels: labels.FromStrings("__name__", "test_metric4", "foo", "bar", "dup", "1"),
1599					Exemplars: []exemplar.Exemplar{
1600						{
1601							Labels: labels.FromStrings("id", "lul"),
1602							Value:  10,
1603							Ts:     timestamp.FromTime(start.Add(4 * time.Second)),
1604						},
1605					},
1606				},
1607			},
1608		},
1609		{
1610			endpoint: api.queryExemplars,
1611			query: url.Values{
1612				"query": []string{`{foo="boo"}`},
1613				"start": []string{"4"},
1614				"end":   []string{"4.1"},
1615			},
1616			response: []exemplar.QueryResult{
1617				{
1618					SeriesLabels: labels.FromStrings("__name__", "test_metric3", "foo", "boo", "dup", "1"),
1619					Exemplars: []exemplar.Exemplar{
1620						{
1621							Labels: labels.FromStrings("id", "abc2"),
1622							Value:  10,
1623							Ts:     4053,
1624						},
1625					},
1626				},
1627			},
1628		},
1629		{
1630			endpoint: api.queryExemplars,
1631			query: url.Values{
1632				"query": []string{`{foo="boo"}`},
1633			},
1634			response: []exemplar.QueryResult{
1635				{
1636					SeriesLabels: labels.FromStrings("__name__", "test_metric3", "foo", "boo", "dup", "1"),
1637					Exemplars: []exemplar.Exemplar{
1638						{
1639							Labels: labels.FromStrings("id", "abc"),
1640							Value:  10,
1641							Ts:     2000,
1642						},
1643						{
1644							Labels: labels.FromStrings("id", "abc2"),
1645							Value:  10,
1646							Ts:     4053,
1647						},
1648					},
1649				},
1650			},
1651		},
1652		{
1653			endpoint: api.queryExemplars,
1654			query: url.Values{
1655				"query": []string{`{__name__="test_metric5"}`},
1656			},
1657			response: []exemplar.QueryResult{},
1658		},
1659	}
1660
1661	if testLabelAPI {
1662		tests = append(tests, []test{
1663			{
1664				endpoint: api.labelValues,
1665				params: map[string]string{
1666					"name": "__name__",
1667				},
1668				response: []string{
1669					"test_metric1",
1670					"test_metric2",
1671					"test_metric3",
1672					"test_metric4",
1673				},
1674			},
1675			{
1676				endpoint: api.labelValues,
1677				params: map[string]string{
1678					"name": "foo",
1679				},
1680				response: []string{
1681					"bar",
1682					"boo",
1683				},
1684			},
1685			// Bad name parameter.
1686			{
1687				endpoint: api.labelValues,
1688				params: map[string]string{
1689					"name": "not!!!allowed",
1690				},
1691				errType: errorBadData,
1692			},
1693			// Start and end before LabelValues starts.
1694			{
1695				endpoint: api.labelValues,
1696				params: map[string]string{
1697					"name": "foo",
1698				},
1699				query: url.Values{
1700					"start": []string{"-2"},
1701					"end":   []string{"-1"},
1702				},
1703				response: []string{},
1704			},
1705			// Start and end within LabelValues.
1706			{
1707				endpoint: api.labelValues,
1708				params: map[string]string{
1709					"name": "foo",
1710				},
1711				query: url.Values{
1712					"start": []string{"1"},
1713					"end":   []string{"100"},
1714				},
1715				response: []string{
1716					"bar",
1717					"boo",
1718				},
1719			},
1720			// Start before LabelValues, end within LabelValues.
1721			{
1722				endpoint: api.labelValues,
1723				params: map[string]string{
1724					"name": "foo",
1725				},
1726				query: url.Values{
1727					"start": []string{"-1"},
1728					"end":   []string{"3"},
1729				},
1730				response: []string{
1731					"bar",
1732					"boo",
1733				},
1734			},
1735			// Start before LabelValues starts, end after LabelValues ends.
1736			{
1737				endpoint: api.labelValues,
1738				params: map[string]string{
1739					"name": "foo",
1740				},
1741				query: url.Values{
1742					"start": []string{"1969-12-31T00:00:00Z"},
1743					"end":   []string{"1970-02-01T00:02:03Z"},
1744				},
1745				response: []string{
1746					"bar",
1747					"boo",
1748				},
1749			},
1750			// Start with bad data, end within LabelValues.
1751			{
1752				endpoint: api.labelValues,
1753				params: map[string]string{
1754					"name": "foo",
1755				},
1756				query: url.Values{
1757					"start": []string{"boop"},
1758					"end":   []string{"1"},
1759				},
1760				errType: errorBadData,
1761			},
1762			// Start within LabelValues, end after.
1763			{
1764				endpoint: api.labelValues,
1765				params: map[string]string{
1766					"name": "foo",
1767				},
1768				query: url.Values{
1769					"start": []string{"1"},
1770					"end":   []string{"100000000"},
1771				},
1772				response: []string{
1773					"bar",
1774					"boo",
1775				},
1776			},
1777			// Start and end after LabelValues ends.
1778			{
1779				endpoint: api.labelValues,
1780				params: map[string]string{
1781					"name": "foo",
1782				},
1783				query: url.Values{
1784					"start": []string{"148966367200.372"},
1785					"end":   []string{"148966367200.972"},
1786				},
1787				response: []string{},
1788			},
1789			// Only provide Start within LabelValues, don't provide an end time.
1790			{
1791				endpoint: api.labelValues,
1792				params: map[string]string{
1793					"name": "foo",
1794				},
1795				query: url.Values{
1796					"start": []string{"2"},
1797				},
1798				response: []string{
1799					"bar",
1800					"boo",
1801				},
1802			},
1803			// Only provide end within LabelValues, don't provide a start time.
1804			{
1805				endpoint: api.labelValues,
1806				params: map[string]string{
1807					"name": "foo",
1808				},
1809				query: url.Values{
1810					"end": []string{"100"},
1811				},
1812				response: []string{
1813					"bar",
1814					"boo",
1815				},
1816			},
1817			// Label values with bad matchers.
1818			{
1819				endpoint: api.labelValues,
1820				params: map[string]string{
1821					"name": "foo",
1822				},
1823				query: url.Values{
1824					"match[]": []string{`{foo=""`, `test_metric2`},
1825				},
1826				errType: errorBadData,
1827			},
1828			// Label values with empty matchers.
1829			{
1830				endpoint: api.labelValues,
1831				params: map[string]string{
1832					"name": "foo",
1833				},
1834				query: url.Values{
1835					"match[]": []string{`{foo=""}`},
1836				},
1837				errType: errorBadData,
1838			},
1839			// Label values with matcher.
1840			{
1841				endpoint: api.labelValues,
1842				params: map[string]string{
1843					"name": "foo",
1844				},
1845				query: url.Values{
1846					"match[]": []string{`test_metric2`},
1847				},
1848				response: []string{
1849					"boo",
1850				},
1851			},
1852			// Label values with matcher.
1853			{
1854				endpoint: api.labelValues,
1855				params: map[string]string{
1856					"name": "foo",
1857				},
1858				query: url.Values{
1859					"match[]": []string{`test_metric1`},
1860				},
1861				response: []string{
1862					"bar",
1863					"boo",
1864				},
1865			},
1866			// Label values with matcher using label filter.
1867			{
1868				endpoint: api.labelValues,
1869				params: map[string]string{
1870					"name": "foo",
1871				},
1872				query: url.Values{
1873					"match[]": []string{`test_metric1{foo="bar"}`},
1874				},
1875				response: []string{
1876					"bar",
1877				},
1878			},
1879			// Label values with matcher and time range.
1880			{
1881				endpoint: api.labelValues,
1882				params: map[string]string{
1883					"name": "foo",
1884				},
1885				query: url.Values{
1886					"match[]": []string{`test_metric1`},
1887					"start":   []string{"1"},
1888					"end":     []string{"100000000"},
1889				},
1890				response: []string{
1891					"bar",
1892					"boo",
1893				},
1894			},
1895			// Try to overlap the selected series set as much as possible to test that the value de-duplication works.
1896			{
1897				endpoint: api.labelValues,
1898				params: map[string]string{
1899					"name": "foo",
1900				},
1901				query: url.Values{
1902					"match[]": []string{`test_metric4{dup=~"^1"}`, `test_metric4{foo=~".+o$"}`},
1903				},
1904				response: []string{
1905					"bar",
1906					"boo",
1907				},
1908			},
1909			// Label names.
1910			{
1911				endpoint: api.labelNames,
1912				response: []string{"__name__", "dup", "foo"},
1913			},
1914			// Start and end before Label names starts.
1915			{
1916				endpoint: api.labelNames,
1917				query: url.Values{
1918					"start": []string{"-2"},
1919					"end":   []string{"-1"},
1920				},
1921				response: []string{},
1922			},
1923			// Start and end within Label names.
1924			{
1925				endpoint: api.labelNames,
1926				query: url.Values{
1927					"start": []string{"1"},
1928					"end":   []string{"100"},
1929				},
1930				response: []string{"__name__", "dup", "foo"},
1931			},
1932			// Start before Label names, end within Label names.
1933			{
1934				endpoint: api.labelNames,
1935				query: url.Values{
1936					"start": []string{"-1"},
1937					"end":   []string{"10"},
1938				},
1939				response: []string{"__name__", "dup", "foo"},
1940			},
1941
1942			// Start before Label names starts, end after Label names ends.
1943			{
1944				endpoint: api.labelNames,
1945				query: url.Values{
1946					"start": []string{"-1"},
1947					"end":   []string{"100000"},
1948				},
1949				response: []string{"__name__", "dup", "foo"},
1950			},
1951			// Start with bad data for Label names, end within Label names.
1952			{
1953				endpoint: api.labelNames,
1954				query: url.Values{
1955					"start": []string{"boop"},
1956					"end":   []string{"1"},
1957				},
1958				errType: errorBadData,
1959			},
1960			// Start within Label names, end after.
1961			{
1962				endpoint: api.labelNames,
1963				query: url.Values{
1964					"start": []string{"1"},
1965					"end":   []string{"1000000006"},
1966				},
1967				response: []string{"__name__", "dup", "foo"},
1968			},
1969			// Start and end after Label names ends.
1970			{
1971				endpoint: api.labelNames,
1972				query: url.Values{
1973					"start": []string{"148966367200.372"},
1974					"end":   []string{"148966367200.972"},
1975				},
1976				response: []string{},
1977			},
1978			// Only provide Start within Label names, don't provide an end time.
1979			{
1980				endpoint: api.labelNames,
1981				query: url.Values{
1982					"start": []string{"4"},
1983				},
1984				response: []string{"__name__", "dup", "foo"},
1985			},
1986			// Only provide End within Label names, don't provide a start time.
1987			{
1988				endpoint: api.labelNames,
1989				query: url.Values{
1990					"end": []string{"20"},
1991				},
1992				response: []string{"__name__", "dup", "foo"},
1993			},
1994			// Label names with bad matchers.
1995			{
1996				endpoint: api.labelNames,
1997				query: url.Values{
1998					"match[]": []string{`{foo=""`, `test_metric2`},
1999				},
2000				errType: errorBadData,
2001			},
2002			// Label values with empty matchers.
2003			{
2004				endpoint: api.labelNames,
2005				params: map[string]string{
2006					"name": "foo",
2007				},
2008				query: url.Values{
2009					"match[]": []string{`{foo=""}`},
2010				},
2011				errType: errorBadData,
2012			},
2013			// Label names with matcher.
2014			{
2015				endpoint: api.labelNames,
2016				query: url.Values{
2017					"match[]": []string{`test_metric2`},
2018				},
2019				response: []string{"__name__", "foo"},
2020			},
2021			// Label names with matcher.
2022			{
2023				endpoint: api.labelNames,
2024				query: url.Values{
2025					"match[]": []string{`test_metric3`},
2026				},
2027				response: []string{"__name__", "dup", "foo"},
2028			},
2029			// Label names with matcher using label filter.
2030			// There is no matching series.
2031			{
2032				endpoint: api.labelNames,
2033				query: url.Values{
2034					"match[]": []string{`test_metric1{foo="test"}`},
2035				},
2036				response: []string{},
2037			},
2038			// Label names with matcher and time range.
2039			{
2040				endpoint: api.labelNames,
2041				query: url.Values{
2042					"match[]": []string{`test_metric2`},
2043					"start":   []string{"1"},
2044					"end":     []string{"100000000"},
2045				},
2046				response: []string{"__name__", "foo"},
2047			},
2048		}...)
2049	}
2050
2051	methods := func(f apiFunc) []string {
2052		fp := reflect.ValueOf(f).Pointer()
2053		if fp == reflect.ValueOf(api.query).Pointer() || fp == reflect.ValueOf(api.queryRange).Pointer() || fp == reflect.ValueOf(api.series).Pointer() {
2054			return []string{http.MethodGet, http.MethodPost}
2055		}
2056		return []string{http.MethodGet}
2057	}
2058
2059	request := func(m string, q url.Values) (*http.Request, error) {
2060		if m == http.MethodPost {
2061			r, err := http.NewRequest(m, "http://example.com", strings.NewReader(q.Encode()))
2062			r.Header.Set("Content-Type", "application/x-www-form-urlencoded")
2063			r.RemoteAddr = "127.0.0.1:20201"
2064			return r, err
2065		}
2066		r, err := http.NewRequest(m, fmt.Sprintf("http://example.com?%s", q.Encode()), nil)
2067		r.RemoteAddr = "127.0.0.1:20201"
2068		return r, err
2069	}
2070
2071	for i, test := range tests {
2072		t.Run(fmt.Sprintf("run %d %s %q", i, describeAPIFunc(test.endpoint), test.query.Encode()), func(t *testing.T) {
2073			for _, method := range methods(test.endpoint) {
2074				t.Run(method, func(t *testing.T) {
2075					// Build a context with the correct request params.
2076					ctx := context.Background()
2077					for p, v := range test.params {
2078						ctx = route.WithParam(ctx, p, v)
2079					}
2080
2081					req, err := request(method, test.query)
2082					if err != nil {
2083						t.Fatal(err)
2084					}
2085
2086					tr.ResetMetadataStore()
2087					for _, tm := range test.metadata {
2088						tr.SetMetadataStoreForTargets(tm.identifier, &testMetaStore{Metadata: tm.metadata})
2089					}
2090
2091					for _, te := range test.exemplars {
2092						for _, e := range te.Exemplars {
2093							_, err := es.AppendExemplar(0, te.SeriesLabels, e)
2094							if err != nil {
2095								t.Fatal(err)
2096							}
2097						}
2098					}
2099
2100					res := test.endpoint(req.WithContext(ctx))
2101					assertAPIError(t, res.err, test.errType)
2102
2103					if test.sorter != nil {
2104						test.sorter(res.data)
2105					}
2106
2107					if test.responseLen != 0 {
2108						assertAPIResponseLength(t, res.data, test.responseLen)
2109					} else {
2110						assertAPIResponse(t, res.data, test.response)
2111					}
2112				})
2113			}
2114		})
2115	}
2116}
2117
2118func describeAPIFunc(f apiFunc) string {
2119	name := runtime.FuncForPC(reflect.ValueOf(f).Pointer()).Name()
2120	return strings.Split(name[strings.LastIndex(name, ".")+1:], "-")[0]
2121}
2122
2123func assertAPIError(t *testing.T, got *apiError, exp errorType) {
2124	t.Helper()
2125
2126	if got != nil {
2127		if exp == errorNone {
2128			t.Fatalf("Unexpected error: %s", got)
2129		}
2130		if exp != got.typ {
2131			t.Fatalf("Expected error of type %q but got type %q (%q)", exp, got.typ, got)
2132		}
2133		return
2134	}
2135	if exp != errorNone {
2136		t.Fatalf("Expected error of type %q but got none", exp)
2137	}
2138}
2139
2140func assertAPIResponse(t *testing.T, got interface{}, exp interface{}) {
2141	t.Helper()
2142
2143	require.Equal(t, exp, got)
2144}
2145
2146func assertAPIResponseLength(t *testing.T, got interface{}, expLen int) {
2147	t.Helper()
2148
2149	gotLen := reflect.ValueOf(got).Len()
2150	if gotLen != expLen {
2151		t.Fatalf(
2152			"Response length does not match, expected:\n%d\ngot:\n%d",
2153			expLen,
2154			gotLen,
2155		)
2156	}
2157}
2158
2159type fakeDB struct {
2160	err error
2161}
2162
2163func (f *fakeDB) CleanTombstones() error                               { return f.err }
2164func (f *fakeDB) Delete(mint, maxt int64, ms ...*labels.Matcher) error { return f.err }
2165func (f *fakeDB) Snapshot(dir string, withHead bool) error             { return f.err }
2166func (f *fakeDB) Stats(statsByLabelName string) (_ *tsdb.Stats, retErr error) {
2167	dbDir, err := ioutil.TempDir("", "tsdb-api-ready")
2168	if err != nil {
2169		return nil, err
2170	}
2171	defer func() {
2172		err := os.RemoveAll(dbDir)
2173		if retErr != nil {
2174			retErr = err
2175		}
2176	}()
2177	opts := tsdb.DefaultHeadOptions()
2178	opts.ChunkRange = 1000
2179	h, _ := tsdb.NewHead(nil, nil, nil, opts, nil)
2180	return h.Stats(statsByLabelName), nil
2181}
2182func (f *fakeDB) WALReplayStatus() (tsdb.WALReplayStatus, error) {
2183	return tsdb.WALReplayStatus{}, nil
2184}
2185
2186func TestAdminEndpoints(t *testing.T) {
2187	tsdb, tsdbWithError, tsdbNotReady := &fakeDB{}, &fakeDB{err: errors.New("some error")}, &fakeDB{err: errors.Wrap(tsdb.ErrNotReady, "wrap")}
2188	snapshotAPI := func(api *API) apiFunc { return api.snapshot }
2189	cleanAPI := func(api *API) apiFunc { return api.cleanTombstones }
2190	deleteAPI := func(api *API) apiFunc { return api.deleteSeries }
2191
2192	for _, tc := range []struct {
2193		db          *fakeDB
2194		enableAdmin bool
2195		endpoint    func(api *API) apiFunc
2196		method      string
2197		values      url.Values
2198
2199		errType errorType
2200	}{
2201		// Tests for the snapshot endpoint.
2202		{
2203			db:          tsdb,
2204			enableAdmin: false,
2205			endpoint:    snapshotAPI,
2206
2207			errType: errorUnavailable,
2208		},
2209		{
2210			db:          tsdb,
2211			enableAdmin: true,
2212			endpoint:    snapshotAPI,
2213
2214			errType: errorNone,
2215		},
2216		{
2217			db:          tsdb,
2218			enableAdmin: true,
2219			endpoint:    snapshotAPI,
2220			values:      map[string][]string{"skip_head": {"true"}},
2221
2222			errType: errorNone,
2223		},
2224		{
2225			db:          tsdb,
2226			enableAdmin: true,
2227			endpoint:    snapshotAPI,
2228			values:      map[string][]string{"skip_head": {"xxx"}},
2229
2230			errType: errorBadData,
2231		},
2232		{
2233			db:          tsdbWithError,
2234			enableAdmin: true,
2235			endpoint:    snapshotAPI,
2236
2237			errType: errorInternal,
2238		},
2239		{
2240			db:          tsdbNotReady,
2241			enableAdmin: true,
2242			endpoint:    snapshotAPI,
2243
2244			errType: errorUnavailable,
2245		},
2246		// Tests for the cleanTombstones endpoint.
2247		{
2248			db:          tsdb,
2249			enableAdmin: false,
2250			endpoint:    cleanAPI,
2251
2252			errType: errorUnavailable,
2253		},
2254		{
2255			db:          tsdb,
2256			enableAdmin: true,
2257			endpoint:    cleanAPI,
2258
2259			errType: errorNone,
2260		},
2261		{
2262			db:          tsdbWithError,
2263			enableAdmin: true,
2264			endpoint:    cleanAPI,
2265
2266			errType: errorInternal,
2267		},
2268		{
2269			db:          tsdbNotReady,
2270			enableAdmin: true,
2271			endpoint:    cleanAPI,
2272
2273			errType: errorUnavailable,
2274		},
2275		// Tests for the deleteSeries endpoint.
2276		{
2277			db:          tsdb,
2278			enableAdmin: false,
2279			endpoint:    deleteAPI,
2280
2281			errType: errorUnavailable,
2282		},
2283		{
2284			db:          tsdb,
2285			enableAdmin: true,
2286			endpoint:    deleteAPI,
2287
2288			errType: errorBadData,
2289		},
2290		{
2291			db:          tsdb,
2292			enableAdmin: true,
2293			endpoint:    deleteAPI,
2294			values:      map[string][]string{"match[]": {"123"}},
2295
2296			errType: errorBadData,
2297		},
2298		{
2299			db:          tsdb,
2300			enableAdmin: true,
2301			endpoint:    deleteAPI,
2302			values:      map[string][]string{"match[]": {"up"}, "start": {"xxx"}},
2303
2304			errType: errorBadData,
2305		},
2306		{
2307			db:          tsdb,
2308			enableAdmin: true,
2309			endpoint:    deleteAPI,
2310			values:      map[string][]string{"match[]": {"up"}, "end": {"xxx"}},
2311
2312			errType: errorBadData,
2313		},
2314		{
2315			db:          tsdb,
2316			enableAdmin: true,
2317			endpoint:    deleteAPI,
2318			values:      map[string][]string{"match[]": {"up"}},
2319
2320			errType: errorNone,
2321		},
2322		{
2323			db:          tsdb,
2324			enableAdmin: true,
2325			endpoint:    deleteAPI,
2326			values:      map[string][]string{"match[]": {"up{job!=\"foo\"}", "{job=~\"bar.+\"}", "up{instance!~\"fred.+\"}"}},
2327
2328			errType: errorNone,
2329		},
2330		{
2331			db:          tsdbWithError,
2332			enableAdmin: true,
2333			endpoint:    deleteAPI,
2334			values:      map[string][]string{"match[]": {"up"}},
2335
2336			errType: errorInternal,
2337		},
2338		{
2339			db:          tsdbNotReady,
2340			enableAdmin: true,
2341			endpoint:    deleteAPI,
2342			values:      map[string][]string{"match[]": {"up"}},
2343
2344			errType: errorUnavailable,
2345		},
2346	} {
2347		tc := tc
2348		t.Run("", func(t *testing.T) {
2349			dir, _ := ioutil.TempDir("", "fakeDB")
2350			defer func() { require.NoError(t, os.RemoveAll(dir)) }()
2351
2352			api := &API{
2353				db:          tc.db,
2354				dbDir:       dir,
2355				ready:       func(f http.HandlerFunc) http.HandlerFunc { return f },
2356				enableAdmin: tc.enableAdmin,
2357			}
2358
2359			endpoint := tc.endpoint(api)
2360			req, err := http.NewRequest(tc.method, fmt.Sprintf("?%s", tc.values.Encode()), nil)
2361			require.NoError(t, err)
2362
2363			res := setUnavailStatusOnTSDBNotReady(endpoint(req))
2364			assertAPIError(t, res.err, tc.errType)
2365		})
2366	}
2367}
2368
2369func TestRespondSuccess(t *testing.T) {
2370	s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
2371		api := API{}
2372		api.respond(w, "test", nil)
2373	}))
2374	defer s.Close()
2375
2376	resp, err := http.Get(s.URL)
2377	if err != nil {
2378		t.Fatalf("Error on test request: %s", err)
2379	}
2380	body, err := ioutil.ReadAll(resp.Body)
2381	defer resp.Body.Close()
2382	if err != nil {
2383		t.Fatalf("Error reading response body: %s", err)
2384	}
2385
2386	if resp.StatusCode != 200 {
2387		t.Fatalf("Return code %d expected in success response but got %d", 200, resp.StatusCode)
2388	}
2389	if h := resp.Header.Get("Content-Type"); h != "application/json" {
2390		t.Fatalf("Expected Content-Type %q but got %q", "application/json", h)
2391	}
2392
2393	var res response
2394	if err = json.Unmarshal([]byte(body), &res); err != nil {
2395		t.Fatalf("Error unmarshaling JSON body: %s", err)
2396	}
2397
2398	exp := &response{
2399		Status: statusSuccess,
2400		Data:   "test",
2401	}
2402	require.Equal(t, exp, &res)
2403}
2404
2405func TestRespondError(t *testing.T) {
2406	s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
2407		api := API{}
2408		api.respondError(w, &apiError{errorTimeout, errors.New("message")}, "test")
2409	}))
2410	defer s.Close()
2411
2412	resp, err := http.Get(s.URL)
2413	if err != nil {
2414		t.Fatalf("Error on test request: %s", err)
2415	}
2416	body, err := ioutil.ReadAll(resp.Body)
2417	defer resp.Body.Close()
2418	if err != nil {
2419		t.Fatalf("Error reading response body: %s", err)
2420	}
2421
2422	if want, have := http.StatusServiceUnavailable, resp.StatusCode; want != have {
2423		t.Fatalf("Return code %d expected in error response but got %d", want, have)
2424	}
2425	if h := resp.Header.Get("Content-Type"); h != "application/json" {
2426		t.Fatalf("Expected Content-Type %q but got %q", "application/json", h)
2427	}
2428
2429	var res response
2430	if err = json.Unmarshal([]byte(body), &res); err != nil {
2431		t.Fatalf("Error unmarshaling JSON body: %s", err)
2432	}
2433
2434	exp := &response{
2435		Status:    statusError,
2436		Data:      "test",
2437		ErrorType: errorTimeout,
2438		Error:     "message",
2439	}
2440	require.Equal(t, exp, &res)
2441}
2442
2443func TestParseTimeParam(t *testing.T) {
2444	type resultType struct {
2445		asTime  time.Time
2446		asError func() error
2447	}
2448
2449	ts, err := parseTime("1582468023986")
2450	require.NoError(t, err)
2451
2452	var tests = []struct {
2453		paramName    string
2454		paramValue   string
2455		defaultValue time.Time
2456		result       resultType
2457	}{
2458		{ // When data is valid.
2459			paramName:    "start",
2460			paramValue:   "1582468023986",
2461			defaultValue: minTime,
2462			result: resultType{
2463				asTime:  ts,
2464				asError: nil,
2465			},
2466		},
2467		{ // When data is empty string.
2468			paramName:    "end",
2469			paramValue:   "",
2470			defaultValue: maxTime,
2471			result: resultType{
2472				asTime:  maxTime,
2473				asError: nil,
2474			},
2475		},
2476		{ // When data is not valid.
2477			paramName:    "foo",
2478			paramValue:   "baz",
2479			defaultValue: maxTime,
2480			result: resultType{
2481				asTime: time.Time{},
2482				asError: func() error {
2483					_, err := parseTime("baz")
2484					return errors.Wrapf(err, "Invalid time value for '%s'", "foo")
2485				},
2486			},
2487		},
2488	}
2489
2490	for _, test := range tests {
2491		req, err := http.NewRequest("GET", "localhost:42/foo?"+test.paramName+"="+test.paramValue, nil)
2492		require.NoError(t, err)
2493
2494		result := test.result
2495		asTime, err := parseTimeParam(req, test.paramName, test.defaultValue)
2496
2497		if err != nil {
2498			require.EqualError(t, err, result.asError().Error())
2499		} else {
2500			require.True(t, asTime.Equal(result.asTime), "time as return value: %s not parsed correctly. Expected %s. Actual %s", test.paramValue, result.asTime, asTime)
2501		}
2502	}
2503}
2504
2505func TestParseTime(t *testing.T) {
2506	ts, err := time.Parse(time.RFC3339Nano, "2015-06-03T13:21:58.555Z")
2507	if err != nil {
2508		panic(err)
2509	}
2510
2511	var tests = []struct {
2512		input  string
2513		fail   bool
2514		result time.Time
2515	}{
2516		{
2517			input: "",
2518			fail:  true,
2519		}, {
2520			input: "abc",
2521			fail:  true,
2522		}, {
2523			input: "30s",
2524			fail:  true,
2525		}, {
2526			input:  "123",
2527			result: time.Unix(123, 0),
2528		}, {
2529			input:  "123.123",
2530			result: time.Unix(123, 123000000),
2531		}, {
2532			input:  "2015-06-03T13:21:58.555Z",
2533			result: ts,
2534		}, {
2535			input:  "2015-06-03T14:21:58.555+01:00",
2536			result: ts,
2537		}, {
2538			// Test float rounding.
2539			input:  "1543578564.705",
2540			result: time.Unix(1543578564, 705*1e6),
2541		},
2542		{
2543			input:  minTime.Format(time.RFC3339Nano),
2544			result: minTime,
2545		},
2546		{
2547			input:  maxTime.Format(time.RFC3339Nano),
2548			result: maxTime,
2549		},
2550	}
2551
2552	for _, test := range tests {
2553		ts, err := parseTime(test.input)
2554		if err != nil && !test.fail {
2555			t.Errorf("Unexpected error for %q: %s", test.input, err)
2556			continue
2557		}
2558		if err == nil && test.fail {
2559			t.Errorf("Expected error for %q but got none", test.input)
2560			continue
2561		}
2562		if !test.fail && !ts.Equal(test.result) {
2563			t.Errorf("Expected time %v for input %q but got %v", test.result, test.input, ts)
2564		}
2565	}
2566}
2567
2568func TestParseDuration(t *testing.T) {
2569	var tests = []struct {
2570		input  string
2571		fail   bool
2572		result time.Duration
2573	}{
2574		{
2575			input: "",
2576			fail:  true,
2577		}, {
2578			input: "abc",
2579			fail:  true,
2580		}, {
2581			input: "2015-06-03T13:21:58.555Z",
2582			fail:  true,
2583		}, {
2584			// Internal int64 overflow.
2585			input: "-148966367200.372",
2586			fail:  true,
2587		}, {
2588			// Internal int64 overflow.
2589			input: "148966367200.372",
2590			fail:  true,
2591		}, {
2592			input:  "123",
2593			result: 123 * time.Second,
2594		}, {
2595			input:  "123.333",
2596			result: 123*time.Second + 333*time.Millisecond,
2597		}, {
2598			input:  "15s",
2599			result: 15 * time.Second,
2600		}, {
2601			input:  "5m",
2602			result: 5 * time.Minute,
2603		},
2604	}
2605
2606	for _, test := range tests {
2607		d, err := parseDuration(test.input)
2608		if err != nil && !test.fail {
2609			t.Errorf("Unexpected error for %q: %s", test.input, err)
2610			continue
2611		}
2612		if err == nil && test.fail {
2613			t.Errorf("Expected error for %q but got none", test.input)
2614			continue
2615		}
2616		if !test.fail && d != test.result {
2617			t.Errorf("Expected duration %v for input %q but got %v", test.result, test.input, d)
2618		}
2619	}
2620}
2621
2622func TestOptionsMethod(t *testing.T) {
2623	r := route.New()
2624	api := &API{ready: func(f http.HandlerFunc) http.HandlerFunc { return f }}
2625	api.Register(r)
2626
2627	s := httptest.NewServer(r)
2628	defer s.Close()
2629
2630	req, err := http.NewRequest("OPTIONS", s.URL+"/any_path", nil)
2631	if err != nil {
2632		t.Fatalf("Error creating OPTIONS request: %s", err)
2633	}
2634	client := &http.Client{}
2635	resp, err := client.Do(req)
2636	if err != nil {
2637		t.Fatalf("Error executing OPTIONS request: %s", err)
2638	}
2639
2640	if resp.StatusCode != http.StatusNoContent {
2641		t.Fatalf("Expected status %d, got %d", http.StatusNoContent, resp.StatusCode)
2642	}
2643}
2644
2645func TestRespond(t *testing.T) {
2646	cases := []struct {
2647		response interface{}
2648		expected string
2649	}{
2650		{
2651			response: &queryData{
2652				ResultType: parser.ValueTypeMatrix,
2653				Result: promql.Matrix{
2654					promql.Series{
2655						Points: []promql.Point{{V: 1, T: 1000}},
2656						Metric: labels.FromStrings("__name__", "foo"),
2657					},
2658				},
2659			},
2660			expected: `{"status":"success","data":{"resultType":"matrix","result":[{"metric":{"__name__":"foo"},"values":[[1,"1"]]}]}}`,
2661		},
2662		{
2663			response: promql.Point{V: 0, T: 0},
2664			expected: `{"status":"success","data":[0,"0"]}`,
2665		},
2666		{
2667			response: promql.Point{V: 20, T: 1},
2668			expected: `{"status":"success","data":[0.001,"20"]}`,
2669		},
2670		{
2671			response: promql.Point{V: 20, T: 10},
2672			expected: `{"status":"success","data":[0.010,"20"]}`,
2673		},
2674		{
2675			response: promql.Point{V: 20, T: 100},
2676			expected: `{"status":"success","data":[0.100,"20"]}`,
2677		},
2678		{
2679			response: promql.Point{V: 20, T: 1001},
2680			expected: `{"status":"success","data":[1.001,"20"]}`,
2681		},
2682		{
2683			response: promql.Point{V: 20, T: 1010},
2684			expected: `{"status":"success","data":[1.010,"20"]}`,
2685		},
2686		{
2687			response: promql.Point{V: 20, T: 1100},
2688			expected: `{"status":"success","data":[1.100,"20"]}`,
2689		},
2690		{
2691			response: promql.Point{V: 20, T: 12345678123456555},
2692			expected: `{"status":"success","data":[12345678123456.555,"20"]}`,
2693		},
2694		{
2695			response: promql.Point{V: 20, T: -1},
2696			expected: `{"status":"success","data":[-0.001,"20"]}`,
2697		},
2698		{
2699			response: promql.Point{V: math.NaN(), T: 0},
2700			expected: `{"status":"success","data":[0,"NaN"]}`,
2701		},
2702		{
2703			response: promql.Point{V: math.Inf(1), T: 0},
2704			expected: `{"status":"success","data":[0,"+Inf"]}`,
2705		},
2706		{
2707			response: promql.Point{V: math.Inf(-1), T: 0},
2708			expected: `{"status":"success","data":[0,"-Inf"]}`,
2709		},
2710		{
2711			response: promql.Point{V: 1.2345678e6, T: 0},
2712			expected: `{"status":"success","data":[0,"1234567.8"]}`,
2713		},
2714		{
2715			response: promql.Point{V: 1.2345678e-6, T: 0},
2716			expected: `{"status":"success","data":[0,"0.0000012345678"]}`,
2717		},
2718		{
2719			response: promql.Point{V: 1.2345678e-67, T: 0},
2720			expected: `{"status":"success","data":[0,"1.2345678e-67"]}`,
2721		},
2722		{
2723			response: []exemplar.QueryResult{
2724				{
2725					SeriesLabels: labels.FromStrings("foo", "bar"),
2726					Exemplars: []exemplar.Exemplar{
2727						{
2728							Labels: labels.FromStrings("traceID", "abc"),
2729							Value:  100.123,
2730							Ts:     1234,
2731						},
2732					},
2733				},
2734			},
2735			expected: `{"status":"success","data":[{"seriesLabels":{"foo":"bar"},"exemplars":[{"labels":{"traceID":"abc"},"value":"100.123","timestamp":1.234}]}]}`,
2736		},
2737		{
2738			response: []exemplar.QueryResult{
2739				{
2740					SeriesLabels: labels.FromStrings("foo", "bar"),
2741					Exemplars: []exemplar.Exemplar{
2742						{
2743							Labels: labels.FromStrings("traceID", "abc"),
2744							Value:  math.Inf(1),
2745							Ts:     1234,
2746						},
2747					},
2748				},
2749			},
2750			expected: `{"status":"success","data":[{"seriesLabels":{"foo":"bar"},"exemplars":[{"labels":{"traceID":"abc"},"value":"+Inf","timestamp":1.234}]}]}`,
2751		},
2752	}
2753
2754	for _, c := range cases {
2755		s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
2756			api := API{}
2757			api.respond(w, c.response, nil)
2758		}))
2759		defer s.Close()
2760
2761		resp, err := http.Get(s.URL)
2762		if err != nil {
2763			t.Fatalf("Error on test request: %s", err)
2764		}
2765		body, err := ioutil.ReadAll(resp.Body)
2766		defer resp.Body.Close()
2767		if err != nil {
2768			t.Fatalf("Error reading response body: %s", err)
2769		}
2770
2771		if string(body) != c.expected {
2772			t.Fatalf("Expected response \n%v\n but got \n%v\n", c.expected, string(body))
2773		}
2774	}
2775}
2776
2777func TestTSDBStatus(t *testing.T) {
2778	tsdb := &fakeDB{}
2779	tsdbStatusAPI := func(api *API) apiFunc { return api.serveTSDBStatus }
2780
2781	for i, tc := range []struct {
2782		db       *fakeDB
2783		endpoint func(api *API) apiFunc
2784		method   string
2785		values   url.Values
2786
2787		errType errorType
2788	}{
2789		// Tests for the TSDB Status endpoint.
2790		{
2791			db:       tsdb,
2792			endpoint: tsdbStatusAPI,
2793
2794			errType: errorNone,
2795		},
2796	} {
2797		tc := tc
2798		t.Run(fmt.Sprintf("%d", i), func(t *testing.T) {
2799			api := &API{db: tc.db, gatherer: prometheus.DefaultGatherer}
2800			endpoint := tc.endpoint(api)
2801			req, err := http.NewRequest(tc.method, fmt.Sprintf("?%s", tc.values.Encode()), nil)
2802			if err != nil {
2803				t.Fatalf("Error when creating test request: %s", err)
2804			}
2805			res := endpoint(req)
2806			assertAPIError(t, res.err, tc.errType)
2807		})
2808	}
2809}
2810
2811func TestReturnAPIError(t *testing.T) {
2812	cases := []struct {
2813		err      error
2814		expected errorType
2815	}{
2816		{
2817			err:      promql.ErrStorage{Err: errors.New("storage error")},
2818			expected: errorInternal,
2819		}, {
2820			err:      errors.Wrap(promql.ErrStorage{Err: errors.New("storage error")}, "wrapped"),
2821			expected: errorInternal,
2822		}, {
2823			err:      promql.ErrQueryTimeout("timeout error"),
2824			expected: errorTimeout,
2825		}, {
2826			err:      errors.Wrap(promql.ErrQueryTimeout("timeout error"), "wrapped"),
2827			expected: errorTimeout,
2828		}, {
2829			err:      promql.ErrQueryCanceled("canceled error"),
2830			expected: errorCanceled,
2831		}, {
2832			err:      errors.Wrap(promql.ErrQueryCanceled("canceled error"), "wrapped"),
2833			expected: errorCanceled,
2834		}, {
2835			err:      errors.New("exec error"),
2836			expected: errorExec,
2837		},
2838	}
2839
2840	for _, c := range cases {
2841		actual := returnAPIError(c.err)
2842		require.Error(t, actual)
2843		require.Equal(t, c.expected, actual.typ)
2844	}
2845}
2846
2847// This is a global to avoid the benchmark being optimized away.
2848var testResponseWriter = httptest.ResponseRecorder{}
2849
2850func BenchmarkRespond(b *testing.B) {
2851	b.ReportAllocs()
2852	points := []promql.Point{}
2853	for i := 0; i < 10000; i++ {
2854		points = append(points, promql.Point{V: float64(i * 1000000), T: int64(i)})
2855	}
2856	response := &queryData{
2857		ResultType: parser.ValueTypeMatrix,
2858		Result: promql.Matrix{
2859			promql.Series{
2860				Points: points,
2861				Metric: nil,
2862			},
2863		},
2864	}
2865	b.ResetTimer()
2866	api := API{}
2867	for n := 0; n < b.N; n++ {
2868		api.respond(&testResponseWriter, response, nil)
2869	}
2870}
2871
2872func TestGetGlobalURL(t *testing.T) {
2873	mustParseURL := func(t *testing.T, u string) *url.URL {
2874		parsed, err := url.Parse(u)
2875		require.NoError(t, err)
2876		return parsed
2877	}
2878
2879	testcases := []struct {
2880		input    *url.URL
2881		opts     GlobalURLOptions
2882		expected *url.URL
2883		errorful bool
2884	}{
2885		{
2886			mustParseURL(t, "http://127.0.0.1:9090"),
2887			GlobalURLOptions{
2888				ListenAddress: "127.0.0.1:9090",
2889				Host:          "127.0.0.1:9090",
2890				Scheme:        "http",
2891			},
2892			mustParseURL(t, "http://127.0.0.1:9090"),
2893			false,
2894		},
2895		{
2896			mustParseURL(t, "http://127.0.0.1:9090"),
2897			GlobalURLOptions{
2898				ListenAddress: "127.0.0.1:9090",
2899				Host:          "prometheus.io",
2900				Scheme:        "https",
2901			},
2902			mustParseURL(t, "https://prometheus.io"),
2903			false,
2904		},
2905		{
2906			mustParseURL(t, "http://exemple.com"),
2907			GlobalURLOptions{
2908				ListenAddress: "127.0.0.1:9090",
2909				Host:          "prometheus.io",
2910				Scheme:        "https",
2911			},
2912			mustParseURL(t, "http://exemple.com"),
2913			false,
2914		},
2915		{
2916			mustParseURL(t, "http://localhost:8080"),
2917			GlobalURLOptions{
2918				ListenAddress: "127.0.0.1:9090",
2919				Host:          "prometheus.io",
2920				Scheme:        "https",
2921			},
2922			mustParseURL(t, "http://prometheus.io:8080"),
2923			false,
2924		},
2925		{
2926			mustParseURL(t, "http://[::1]:8080"),
2927			GlobalURLOptions{
2928				ListenAddress: "127.0.0.1:9090",
2929				Host:          "prometheus.io",
2930				Scheme:        "https",
2931			},
2932			mustParseURL(t, "http://prometheus.io:8080"),
2933			false,
2934		},
2935		{
2936			mustParseURL(t, "http://localhost"),
2937			GlobalURLOptions{
2938				ListenAddress: "127.0.0.1:9090",
2939				Host:          "prometheus.io",
2940				Scheme:        "https",
2941			},
2942			mustParseURL(t, "http://prometheus.io"),
2943			false,
2944		},
2945		{
2946			mustParseURL(t, "http://localhost:9091"),
2947			GlobalURLOptions{
2948				ListenAddress: "[::1]:9090",
2949				Host:          "[::1]",
2950				Scheme:        "https",
2951			},
2952			mustParseURL(t, "http://[::1]:9091"),
2953			false,
2954		},
2955		{
2956			mustParseURL(t, "http://localhost:9091"),
2957			GlobalURLOptions{
2958				ListenAddress: "[::1]:9090",
2959				Host:          "[::1]:9090",
2960				Scheme:        "https",
2961			},
2962			mustParseURL(t, "http://[::1]:9091"),
2963			false,
2964		},
2965	}
2966
2967	for i, tc := range testcases {
2968		t.Run(fmt.Sprintf("Test %d", i), func(t *testing.T) {
2969			output, err := getGlobalURL(tc.input, tc.opts)
2970			if tc.errorful {
2971				require.Error(t, err)
2972				return
2973			}
2974			require.NoError(t, err)
2975			require.Equal(t, tc.expected, output)
2976		})
2977	}
2978}
2979