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