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