github.com/thanos-io/thanos@v0.32.5/pkg/store/bucket_e2e_test.go (about) 1 // Copyright (c) The Thanos Authors. 2 // Licensed under the Apache License 2.0. 3 4 package store 5 6 import ( 7 "context" 8 "fmt" 9 "math" 10 "os" 11 "path/filepath" 12 "strings" 13 "sync" 14 "testing" 15 "time" 16 17 "github.com/alecthomas/units" 18 "github.com/go-kit/log" 19 "github.com/gogo/status" 20 "github.com/oklog/ulid" 21 "github.com/prometheus/client_golang/prometheus" 22 dto "github.com/prometheus/client_model/go" 23 "github.com/prometheus/prometheus/model/labels" 24 "github.com/prometheus/prometheus/model/relabel" 25 "github.com/prometheus/prometheus/model/timestamp" 26 "github.com/prometheus/prometheus/storage" 27 "google.golang.org/grpc/codes" 28 29 "github.com/thanos-io/objstore" 30 "github.com/thanos-io/objstore/objtesting" 31 32 "github.com/efficientgo/core/testutil" 33 34 "github.com/thanos-io/thanos/pkg/block" 35 "github.com/thanos-io/thanos/pkg/block/metadata" 36 "github.com/thanos-io/thanos/pkg/model" 37 storecache "github.com/thanos-io/thanos/pkg/store/cache" 38 "github.com/thanos-io/thanos/pkg/store/labelpb" 39 "github.com/thanos-io/thanos/pkg/store/storepb" 40 "github.com/thanos-io/thanos/pkg/testutil/e2eutil" 41 ) 42 43 var ( 44 minTime = time.Unix(0, 0) 45 maxTime, _ = time.Parse(time.RFC3339, "9999-12-31T23:59:59Z") 46 minTimeDuration = model.TimeOrDurationValue{Time: &minTime} 47 maxTimeDuration = model.TimeOrDurationValue{Time: &maxTime} 48 allowAllFilterConf = &FilterConfig{ 49 MinTime: minTimeDuration, 50 MaxTime: maxTimeDuration, 51 } 52 ) 53 54 type swappableCache struct { 55 ptr storecache.IndexCache 56 } 57 58 func (c *swappableCache) SwapWith(ptr2 storecache.IndexCache) { 59 c.ptr = ptr2 60 } 61 62 func (c *swappableCache) StorePostings(blockID ulid.ULID, l labels.Label, v []byte) { 63 c.ptr.StorePostings(blockID, l, v) 64 } 65 66 func (c *swappableCache) FetchMultiPostings(ctx context.Context, blockID ulid.ULID, keys []labels.Label) (map[labels.Label][]byte, []labels.Label) { 67 return c.ptr.FetchMultiPostings(ctx, blockID, keys) 68 } 69 70 func (c *swappableCache) StoreExpandedPostings(blockID ulid.ULID, matchers []*labels.Matcher, v []byte) { 71 c.ptr.StoreExpandedPostings(blockID, matchers, v) 72 } 73 74 func (c *swappableCache) FetchExpandedPostings(ctx context.Context, blockID ulid.ULID, matchers []*labels.Matcher) ([]byte, bool) { 75 return c.ptr.FetchExpandedPostings(ctx, blockID, matchers) 76 } 77 78 func (c *swappableCache) StoreSeries(blockID ulid.ULID, id storage.SeriesRef, v []byte) { 79 c.ptr.StoreSeries(blockID, id, v) 80 } 81 82 func (c *swappableCache) FetchMultiSeries(ctx context.Context, blockID ulid.ULID, ids []storage.SeriesRef) (map[storage.SeriesRef][]byte, []storage.SeriesRef) { 83 return c.ptr.FetchMultiSeries(ctx, blockID, ids) 84 } 85 86 type storeSuite struct { 87 store *BucketStore 88 minTime, maxTime int64 89 cache *swappableCache 90 91 logger log.Logger 92 } 93 94 func prepareTestBlocks(t testing.TB, now time.Time, count int, dir string, bkt objstore.Bucket, 95 series []labels.Labels, extLset labels.Labels) (minTime, maxTime int64) { 96 ctx := context.Background() 97 logger := log.NewNopLogger() 98 99 for i := 0; i < count; i++ { 100 mint := timestamp.FromTime(now) 101 now = now.Add(2 * time.Hour) 102 maxt := timestamp.FromTime(now) 103 104 if minTime == 0 { 105 minTime = mint 106 } 107 maxTime = maxt 108 109 // Create two blocks per time slot. Only add 10 samples each so only one chunk 110 // gets created each. This way we can easily verify we got 10 chunks per series below. 111 id1, err := e2eutil.CreateBlock(ctx, dir, series[:4], 10, mint, maxt, extLset, 0, metadata.NoneFunc) 112 testutil.Ok(t, err) 113 id2, err := e2eutil.CreateBlock(ctx, dir, series[4:], 10, mint, maxt, extLset, 0, metadata.NoneFunc) 114 testutil.Ok(t, err) 115 116 dir1, dir2 := filepath.Join(dir, id1.String()), filepath.Join(dir, id2.String()) 117 118 // Replace labels to the meta of the second block. 119 meta, err := metadata.ReadFromDir(dir2) 120 testutil.Ok(t, err) 121 meta.Thanos.Labels = map[string]string{"ext2": "value2"} 122 testutil.Ok(t, meta.WriteToDir(logger, dir2)) 123 124 testutil.Ok(t, block.Upload(ctx, logger, bkt, dir1, metadata.NoneFunc)) 125 testutil.Ok(t, block.Upload(ctx, logger, bkt, dir2, metadata.NoneFunc)) 126 127 testutil.Ok(t, os.RemoveAll(dir1)) 128 testutil.Ok(t, os.RemoveAll(dir2)) 129 } 130 131 return 132 } 133 134 func prepareStoreWithTestBlocks(t testing.TB, dir string, bkt objstore.Bucket, manyParts bool, chunksLimiterFactory ChunksLimiterFactory, seriesLimiterFactory SeriesLimiterFactory, bytesLimiterFactory BytesLimiterFactory, relabelConfig []*relabel.Config, filterConf *FilterConfig) *storeSuite { 135 series := []labels.Labels{ 136 labels.FromStrings("a", "1", "b", "1"), 137 labels.FromStrings("a", "1", "b", "2"), 138 labels.FromStrings("a", "2", "b", "1"), 139 labels.FromStrings("a", "2", "b", "2"), 140 labels.FromStrings("a", "1", "c", "1"), 141 labels.FromStrings("a", "1", "c", "2"), 142 labels.FromStrings("a", "2", "c", "1"), 143 labels.FromStrings("a", "2", "c", "2"), 144 } 145 extLset := labels.FromStrings("ext1", "value1") 146 147 minTime, maxTime := prepareTestBlocks(t, time.Now(), 3, dir, bkt, series, extLset) 148 149 s := &storeSuite{ 150 logger: log.NewLogfmtLogger(os.Stderr), 151 cache: &swappableCache{}, 152 minTime: minTime, 153 maxTime: maxTime, 154 } 155 156 metaFetcher, err := block.NewMetaFetcher(s.logger, 20, objstore.WithNoopInstr(bkt), dir, nil, []block.MetadataFilter{ 157 block.NewTimePartitionMetaFilter(filterConf.MinTime, filterConf.MaxTime), 158 block.NewLabelShardedMetaFilter(relabelConfig), 159 }) 160 testutil.Ok(t, err) 161 162 reg := prometheus.NewRegistry() 163 store, err := NewBucketStore( 164 objstore.WithNoopInstr(bkt), 165 metaFetcher, 166 dir, 167 chunksLimiterFactory, 168 seriesLimiterFactory, 169 bytesLimiterFactory, 170 NewGapBasedPartitioner(PartitionerMaxGapSize), 171 20, 172 true, 173 DefaultPostingOffsetInMemorySampling, 174 true, 175 true, 176 time.Minute, 177 WithLogger(s.logger), 178 WithIndexCache(s.cache), 179 WithFilterConfig(filterConf), 180 WithRegistry(reg), 181 ) 182 testutil.Ok(t, err) 183 defer func() { testutil.Ok(t, store.Close()) }() 184 185 s.store = store 186 187 if manyParts { 188 s.store.partitioner = naivePartitioner{} 189 } 190 191 ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) 192 defer cancel() 193 194 // Check if the blocks are being loaded. 195 start := time.Now() 196 time.Sleep(time.Second * 1) 197 testutil.Ok(t, store.SyncBlocks(ctx)) 198 199 // Get the value of the metric 'thanos_bucket_store_blocks_last_loaded_timestamp_seconds' to capture the timestamp of the last loaded block. 200 m := gatherFamily(t, reg, "thanos_bucket_store_blocks_last_loaded_timestamp_seconds") 201 lastUploaded := time.Unix(int64(m.Metric[0].Gauge.GetValue()), 0) 202 203 if lastUploaded.Before(start) { 204 t.Fatalf("no blocks are loaded ") 205 } 206 207 return s 208 } 209 210 func gatherFamily(t testing.TB, reg prometheus.Gatherer, familyName string) *dto.MetricFamily { 211 212 families, err := reg.Gather() 213 testutil.Ok(t, err) 214 215 for _, f := range families { 216 if f.GetName() == familyName { 217 return f 218 } 219 } 220 221 t.Fatalf("could not find family %s", familyName) 222 return nil 223 } 224 225 func testBucketStore_e2e(t *testing.T, ctx context.Context, s *storeSuite) { 226 t.Helper() 227 228 mint, maxt := s.store.TimeRange() 229 testutil.Equals(t, s.minTime, mint) 230 testutil.Equals(t, s.maxTime, maxt) 231 232 vals, err := s.store.LabelValues(ctx, &storepb.LabelValuesRequest{ 233 Label: "a", 234 Start: timestamp.FromTime(minTime), 235 End: timestamp.FromTime(maxTime), 236 }) 237 testutil.Ok(t, err) 238 testutil.Equals(t, []string{"1", "2"}, vals.Values) 239 240 // TODO(bwplotka): Add those test cases to TSDB querier_test.go as well, there are no tests for matching. 241 for i, tcase := range []struct { 242 req *storepb.SeriesRequest 243 expected [][]labelpb.ZLabel 244 expectedChunkLen int 245 }{ 246 { 247 req: &storepb.SeriesRequest{ 248 Matchers: []storepb.LabelMatcher{ 249 {Type: storepb.LabelMatcher_RE, Name: "a", Value: "1|2"}, 250 }, 251 MinTime: mint, 252 MaxTime: maxt, 253 }, 254 expectedChunkLen: 3, 255 expected: [][]labelpb.ZLabel{ 256 {{Name: "a", Value: "1"}, {Name: "b", Value: "1"}, {Name: "ext1", Value: "value1"}}, 257 {{Name: "a", Value: "1"}, {Name: "b", Value: "2"}, {Name: "ext1", Value: "value1"}}, 258 {{Name: "a", Value: "1"}, {Name: "c", Value: "1"}, {Name: "ext2", Value: "value2"}}, 259 {{Name: "a", Value: "1"}, {Name: "c", Value: "2"}, {Name: "ext2", Value: "value2"}}, 260 {{Name: "a", Value: "2"}, {Name: "b", Value: "1"}, {Name: "ext1", Value: "value1"}}, 261 {{Name: "a", Value: "2"}, {Name: "b", Value: "2"}, {Name: "ext1", Value: "value1"}}, 262 {{Name: "a", Value: "2"}, {Name: "c", Value: "1"}, {Name: "ext2", Value: "value2"}}, 263 {{Name: "a", Value: "2"}, {Name: "c", Value: "2"}, {Name: "ext2", Value: "value2"}}, 264 }, 265 }, 266 { 267 req: &storepb.SeriesRequest{ 268 Matchers: []storepb.LabelMatcher{ 269 {Type: storepb.LabelMatcher_RE, Name: "a", Value: "1|2"}, 270 }, 271 MinTime: mint, 272 MaxTime: maxt, 273 WithoutReplicaLabels: []string{"ext1", "ext2"}, 274 }, 275 expectedChunkLen: 3, 276 expected: [][]labelpb.ZLabel{ 277 {{Name: "a", Value: "1"}, {Name: "b", Value: "1"}}, 278 {{Name: "a", Value: "1"}, {Name: "b", Value: "2"}}, 279 {{Name: "a", Value: "1"}, {Name: "c", Value: "1"}}, 280 {{Name: "a", Value: "1"}, {Name: "c", Value: "2"}}, 281 {{Name: "a", Value: "2"}, {Name: "b", Value: "1"}}, 282 {{Name: "a", Value: "2"}, {Name: "b", Value: "2"}}, 283 {{Name: "a", Value: "2"}, {Name: "c", Value: "1"}}, 284 {{Name: "a", Value: "2"}, {Name: "c", Value: "2"}}, 285 }, 286 }, 287 { 288 req: &storepb.SeriesRequest{ 289 Matchers: []storepb.LabelMatcher{ 290 {Type: storepb.LabelMatcher_RE, Name: "a", Value: "1"}, 291 }, 292 MinTime: mint, 293 MaxTime: maxt, 294 }, 295 expectedChunkLen: 3, 296 expected: [][]labelpb.ZLabel{ 297 {{Name: "a", Value: "1"}, {Name: "b", Value: "1"}, {Name: "ext1", Value: "value1"}}, 298 {{Name: "a", Value: "1"}, {Name: "b", Value: "2"}, {Name: "ext1", Value: "value1"}}, 299 {{Name: "a", Value: "1"}, {Name: "c", Value: "1"}, {Name: "ext2", Value: "value2"}}, 300 {{Name: "a", Value: "1"}, {Name: "c", Value: "2"}, {Name: "ext2", Value: "value2"}}, 301 }, 302 }, 303 { 304 req: &storepb.SeriesRequest{ 305 Matchers: []storepb.LabelMatcher{ 306 {Type: storepb.LabelMatcher_NRE, Name: "a", Value: "2"}, 307 }, 308 MinTime: mint, 309 MaxTime: maxt, 310 }, 311 expectedChunkLen: 3, 312 expected: [][]labelpb.ZLabel{ 313 {{Name: "a", Value: "1"}, {Name: "b", Value: "1"}, {Name: "ext1", Value: "value1"}}, 314 {{Name: "a", Value: "1"}, {Name: "b", Value: "2"}, {Name: "ext1", Value: "value1"}}, 315 {{Name: "a", Value: "1"}, {Name: "c", Value: "1"}, {Name: "ext2", Value: "value2"}}, 316 {{Name: "a", Value: "1"}, {Name: "c", Value: "2"}, {Name: "ext2", Value: "value2"}}, 317 }, 318 }, 319 { 320 req: &storepb.SeriesRequest{ 321 Matchers: []storepb.LabelMatcher{ 322 {Type: storepb.LabelMatcher_NRE, Name: "a", Value: "not_existing"}, 323 }, 324 MinTime: mint, 325 MaxTime: maxt, 326 }, 327 expectedChunkLen: 3, 328 expected: [][]labelpb.ZLabel{ 329 {{Name: "a", Value: "1"}, {Name: "b", Value: "1"}, {Name: "ext1", Value: "value1"}}, 330 {{Name: "a", Value: "1"}, {Name: "b", Value: "2"}, {Name: "ext1", Value: "value1"}}, 331 {{Name: "a", Value: "1"}, {Name: "c", Value: "1"}, {Name: "ext2", Value: "value2"}}, 332 {{Name: "a", Value: "1"}, {Name: "c", Value: "2"}, {Name: "ext2", Value: "value2"}}, 333 {{Name: "a", Value: "2"}, {Name: "b", Value: "1"}, {Name: "ext1", Value: "value1"}}, 334 {{Name: "a", Value: "2"}, {Name: "b", Value: "2"}, {Name: "ext1", Value: "value1"}}, 335 {{Name: "a", Value: "2"}, {Name: "c", Value: "1"}, {Name: "ext2", Value: "value2"}}, 336 {{Name: "a", Value: "2"}, {Name: "c", Value: "2"}, {Name: "ext2", Value: "value2"}}, 337 }, 338 }, 339 { 340 req: &storepb.SeriesRequest{ 341 Matchers: []storepb.LabelMatcher{ 342 {Type: storepb.LabelMatcher_NRE, Name: "not_existing", Value: "1"}, 343 }, 344 MinTime: mint, 345 MaxTime: maxt, 346 }, 347 expectedChunkLen: 3, 348 expected: [][]labelpb.ZLabel{ 349 {{Name: "a", Value: "1"}, {Name: "b", Value: "1"}, {Name: "ext1", Value: "value1"}}, 350 {{Name: "a", Value: "1"}, {Name: "b", Value: "2"}, {Name: "ext1", Value: "value1"}}, 351 {{Name: "a", Value: "1"}, {Name: "c", Value: "1"}, {Name: "ext2", Value: "value2"}}, 352 {{Name: "a", Value: "1"}, {Name: "c", Value: "2"}, {Name: "ext2", Value: "value2"}}, 353 {{Name: "a", Value: "2"}, {Name: "b", Value: "1"}, {Name: "ext1", Value: "value1"}}, 354 {{Name: "a", Value: "2"}, {Name: "b", Value: "2"}, {Name: "ext1", Value: "value1"}}, 355 {{Name: "a", Value: "2"}, {Name: "c", Value: "1"}, {Name: "ext2", Value: "value2"}}, 356 {{Name: "a", Value: "2"}, {Name: "c", Value: "2"}, {Name: "ext2", Value: "value2"}}, 357 }, 358 }, 359 { 360 req: &storepb.SeriesRequest{ 361 Matchers: []storepb.LabelMatcher{ 362 {Type: storepb.LabelMatcher_EQ, Name: "b", Value: "2"}, 363 }, 364 MinTime: mint, 365 MaxTime: maxt, 366 }, 367 expectedChunkLen: 3, 368 expected: [][]labelpb.ZLabel{ 369 {{Name: "a", Value: "1"}, {Name: "b", Value: "2"}, {Name: "ext1", Value: "value1"}}, 370 {{Name: "a", Value: "2"}, {Name: "b", Value: "2"}, {Name: "ext1", Value: "value1"}}, 371 }, 372 }, 373 { 374 // Matching by external label should work as well. 375 req: &storepb.SeriesRequest{ 376 Matchers: []storepb.LabelMatcher{ 377 {Type: storepb.LabelMatcher_EQ, Name: "a", Value: "1"}, 378 {Type: storepb.LabelMatcher_EQ, Name: "ext2", Value: "value2"}, 379 }, 380 MinTime: mint, 381 MaxTime: maxt, 382 }, 383 expectedChunkLen: 3, 384 expected: [][]labelpb.ZLabel{ 385 {{Name: "a", Value: "1"}, {Name: "c", Value: "1"}, {Name: "ext2", Value: "value2"}}, 386 {{Name: "a", Value: "1"}, {Name: "c", Value: "2"}, {Name: "ext2", Value: "value2"}}, 387 }, 388 }, 389 { 390 req: &storepb.SeriesRequest{ 391 Matchers: []storepb.LabelMatcher{ 392 {Type: storepb.LabelMatcher_EQ, Name: "a", Value: "1"}, 393 {Type: storepb.LabelMatcher_EQ, Name: "ext2", Value: "wrong-value"}, 394 }, 395 MinTime: mint, 396 MaxTime: maxt, 397 }, 398 }, 399 { 400 req: &storepb.SeriesRequest{ 401 Matchers: []storepb.LabelMatcher{ 402 {Type: storepb.LabelMatcher_NEQ, Name: "a", Value: "2"}, 403 }, 404 MinTime: mint, 405 MaxTime: maxt, 406 }, 407 expectedChunkLen: 3, 408 expected: [][]labelpb.ZLabel{ 409 {{Name: "a", Value: "1"}, {Name: "b", Value: "1"}, {Name: "ext1", Value: "value1"}}, 410 {{Name: "a", Value: "1"}, {Name: "b", Value: "2"}, {Name: "ext1", Value: "value1"}}, 411 {{Name: "a", Value: "1"}, {Name: "c", Value: "1"}, {Name: "ext2", Value: "value2"}}, 412 {{Name: "a", Value: "1"}, {Name: "c", Value: "2"}, {Name: "ext2", Value: "value2"}}, 413 }, 414 }, 415 { 416 req: &storepb.SeriesRequest{ 417 Matchers: []storepb.LabelMatcher{ 418 {Type: storepb.LabelMatcher_NEQ, Name: "a", Value: "not_existing"}, 419 }, 420 MinTime: mint, 421 MaxTime: maxt, 422 }, 423 expectedChunkLen: 3, 424 expected: [][]labelpb.ZLabel{ 425 {{Name: "a", Value: "1"}, {Name: "b", Value: "1"}, {Name: "ext1", Value: "value1"}}, 426 {{Name: "a", Value: "1"}, {Name: "b", Value: "2"}, {Name: "ext1", Value: "value1"}}, 427 {{Name: "a", Value: "1"}, {Name: "c", Value: "1"}, {Name: "ext2", Value: "value2"}}, 428 {{Name: "a", Value: "1"}, {Name: "c", Value: "2"}, {Name: "ext2", Value: "value2"}}, 429 {{Name: "a", Value: "2"}, {Name: "b", Value: "1"}, {Name: "ext1", Value: "value1"}}, 430 {{Name: "a", Value: "2"}, {Name: "b", Value: "2"}, {Name: "ext1", Value: "value1"}}, 431 {{Name: "a", Value: "2"}, {Name: "c", Value: "1"}, {Name: "ext2", Value: "value2"}}, 432 {{Name: "a", Value: "2"}, {Name: "c", Value: "2"}, {Name: "ext2", Value: "value2"}}, 433 }, 434 }, 435 // Regression https://github.com/thanos-io/thanos/issues/833. 436 // Problem: Matcher that was selecting NO series, was ignored instead of passed as emptyPosting to Intersect. 437 { 438 req: &storepb.SeriesRequest{ 439 Matchers: []storepb.LabelMatcher{ 440 {Type: storepb.LabelMatcher_EQ, Name: "a", Value: "1"}, 441 {Type: storepb.LabelMatcher_RE, Name: "non_existing", Value: "something"}, 442 }, 443 MinTime: mint, 444 MaxTime: maxt, 445 }, 446 }, 447 // Test skip-chunk option. 448 { 449 req: &storepb.SeriesRequest{ 450 Matchers: []storepb.LabelMatcher{ 451 {Type: storepb.LabelMatcher_EQ, Name: "a", Value: "1"}, 452 }, 453 MinTime: mint, 454 MaxTime: maxt, 455 SkipChunks: true, 456 }, 457 expectedChunkLen: 0, 458 expected: [][]labelpb.ZLabel{ 459 {{Name: "a", Value: "1"}, {Name: "b", Value: "1"}, {Name: "ext1", Value: "value1"}}, 460 {{Name: "a", Value: "1"}, {Name: "b", Value: "2"}, {Name: "ext1", Value: "value1"}}, 461 {{Name: "a", Value: "1"}, {Name: "c", Value: "1"}, {Name: "ext2", Value: "value2"}}, 462 {{Name: "a", Value: "1"}, {Name: "c", Value: "2"}, {Name: "ext2", Value: "value2"}}, 463 }, 464 }, 465 } { 466 if ok := t.Run(fmt.Sprint(i), func(t *testing.T) { 467 srv := newStoreSeriesServer(ctx) 468 469 testutil.Ok(t, s.store.Series(tcase.req, srv)) 470 testutil.Equals(t, len(tcase.expected), len(srv.SeriesSet)) 471 472 for i, s := range srv.SeriesSet { 473 testutil.Equals(t, tcase.expected[i], s.Labels) 474 testutil.Equals(t, tcase.expectedChunkLen, len(s.Chunks)) 475 } 476 }); !ok { 477 return 478 } 479 } 480 } 481 482 func TestBucketStore_e2e(t *testing.T) { 483 objtesting.ForeachStore(t, func(t *testing.T, bkt objstore.Bucket) { 484 ctx, cancel := context.WithCancel(context.Background()) 485 defer cancel() 486 487 dir := t.TempDir() 488 489 s := prepareStoreWithTestBlocks(t, dir, bkt, false, NewChunksLimiterFactory(0), NewSeriesLimiterFactory(0), NewBytesLimiterFactory(0), emptyRelabelConfig, allowAllFilterConf) 490 491 if ok := t.Run("no index cache", func(t *testing.T) { 492 s.cache.SwapWith(noopCache{}) 493 testBucketStore_e2e(t, ctx, s) 494 }); !ok { 495 return 496 } 497 498 if ok := t.Run("with large, sufficient index cache", func(t *testing.T) { 499 indexCache, err := storecache.NewInMemoryIndexCacheWithConfig(s.logger, nil, nil, storecache.InMemoryIndexCacheConfig{ 500 MaxItemSize: 1e5, 501 MaxSize: 2e5, 502 }) 503 testutil.Ok(t, err) 504 s.cache.SwapWith(indexCache) 505 testBucketStore_e2e(t, ctx, s) 506 }); !ok { 507 return 508 } 509 510 t.Run("with small index cache", func(t *testing.T) { 511 indexCache2, err := storecache.NewInMemoryIndexCacheWithConfig(s.logger, nil, nil, storecache.InMemoryIndexCacheConfig{ 512 MaxItemSize: 50, 513 MaxSize: 100, 514 }) 515 testutil.Ok(t, err) 516 s.cache.SwapWith(indexCache2) 517 testBucketStore_e2e(t, ctx, s) 518 }) 519 }) 520 } 521 522 type naivePartitioner struct{} 523 524 func (g naivePartitioner) Partition(length int, rng func(int) (uint64, uint64)) (parts []Part) { 525 for i := 0; i < length; i++ { 526 s, e := rng(i) 527 parts = append(parts, Part{Start: s, End: e, ElemRng: [2]int{i, i + 1}}) 528 } 529 return parts 530 } 531 532 // Naive partitioner splits the array equally (it does not combine anything). 533 // This tests if our, sometimes concurrent, fetches for different parts works. 534 // Regression test against: https://github.com/thanos-io/thanos/issues/829. 535 func TestBucketStore_ManyParts_e2e(t *testing.T) { 536 objtesting.ForeachStore(t, func(t *testing.T, bkt objstore.Bucket) { 537 ctx, cancel := context.WithCancel(context.Background()) 538 defer cancel() 539 540 dir := t.TempDir() 541 542 s := prepareStoreWithTestBlocks(t, dir, bkt, true, NewChunksLimiterFactory(0), NewSeriesLimiterFactory(0), NewBytesLimiterFactory(0), emptyRelabelConfig, allowAllFilterConf) 543 544 indexCache, err := storecache.NewInMemoryIndexCacheWithConfig(s.logger, nil, nil, storecache.InMemoryIndexCacheConfig{ 545 MaxItemSize: 1e5, 546 MaxSize: 2e5, 547 }) 548 testutil.Ok(t, err) 549 s.cache.SwapWith(indexCache) 550 551 testBucketStore_e2e(t, ctx, s) 552 }) 553 } 554 555 func TestBucketStore_TimePartitioning_e2e(t *testing.T) { 556 ctx, cancel := context.WithCancel(context.Background()) 557 defer cancel() 558 bkt := objstore.NewInMemBucket() 559 560 dir := t.TempDir() 561 562 hourAfter := time.Now().Add(1 * time.Hour) 563 filterMaxTime := model.TimeOrDurationValue{Time: &hourAfter} 564 565 // The query will fetch 2 series from 2 blocks, so we do expect to hit a total of 4 chunks. 566 expectedChunks := uint64(2 * 2) 567 568 s := prepareStoreWithTestBlocks(t, dir, bkt, false, NewChunksLimiterFactory(expectedChunks), NewSeriesLimiterFactory(0), NewBytesLimiterFactory(0), emptyRelabelConfig, &FilterConfig{ 569 MinTime: minTimeDuration, 570 MaxTime: filterMaxTime, 571 }) 572 testutil.Ok(t, s.store.SyncBlocks(ctx)) 573 574 mint, maxt := s.store.TimeRange() 575 testutil.Equals(t, s.minTime, mint) 576 testutil.Equals(t, filterMaxTime.PrometheusTimestamp(), maxt) 577 578 req := &storepb.SeriesRequest{ 579 Matchers: []storepb.LabelMatcher{ 580 {Type: storepb.LabelMatcher_EQ, Name: "a", Value: "1"}, 581 }, 582 MinTime: mint, 583 MaxTime: timestamp.FromTime(time.Now().AddDate(0, 0, 1)), 584 } 585 586 expectedLabels := [][]labelpb.ZLabel{ 587 {{Name: "a", Value: "1"}, {Name: "b", Value: "1"}, {Name: "ext1", Value: "value1"}}, 588 {{Name: "a", Value: "1"}, {Name: "b", Value: "2"}, {Name: "ext1", Value: "value1"}}, 589 {{Name: "a", Value: "1"}, {Name: "c", Value: "1"}, {Name: "ext2", Value: "value2"}}, 590 {{Name: "a", Value: "1"}, {Name: "c", Value: "2"}, {Name: "ext2", Value: "value2"}}, 591 } 592 593 s.cache.SwapWith(noopCache{}) 594 srv := newStoreSeriesServer(ctx) 595 596 testutil.Ok(t, s.store.Series(req, srv)) 597 testutil.Equals(t, len(expectedLabels), len(srv.SeriesSet)) 598 599 for i, s := range srv.SeriesSet { 600 testutil.Equals(t, expectedLabels[i], s.Labels) 601 602 // prepareTestBlocks makes 3 chunks containing 2 hour data, 603 // we should only get 1, as we are filtering by time. 604 testutil.Equals(t, 1, len(s.Chunks)) 605 } 606 } 607 608 func TestBucketStore_Series_ChunksLimiter_e2e(t *testing.T) { 609 // The query will fetch 2 series from 6 blocks, so we do expect to hit a total of 12 chunks. 610 expectedChunks := uint64(2 * 6) 611 612 cases := map[string]struct { 613 maxChunksLimit uint64 614 maxSeriesLimit uint64 615 maxBytesLimit int64 616 expectedErr string 617 code codes.Code 618 }{ 619 "should succeed if the max chunks limit is not exceeded": { 620 maxChunksLimit: expectedChunks, 621 }, 622 "should fail if the max chunks limit is exceeded - ResourceExhausted": { 623 maxChunksLimit: expectedChunks - 1, 624 expectedErr: "exceeded chunks limit", 625 code: codes.ResourceExhausted, 626 }, 627 "should fail if the max series limit is exceeded - ResourceExhausted": { 628 maxChunksLimit: expectedChunks, 629 expectedErr: "exceeded series limit", 630 maxSeriesLimit: 1, 631 code: codes.ResourceExhausted, 632 }, 633 "should fail if the max bytes limit is exceeded - ResourceExhausted": { 634 maxChunksLimit: expectedChunks, 635 expectedErr: "exceeded bytes limit", 636 maxSeriesLimit: 2, 637 maxBytesLimit: 1, 638 code: codes.ResourceExhausted, 639 }, 640 } 641 642 for testName, testData := range cases { 643 t.Run(testName, func(t *testing.T) { 644 ctx, cancel := context.WithCancel(context.Background()) 645 defer cancel() 646 bkt := objstore.NewInMemBucket() 647 648 dir := t.TempDir() 649 650 s := prepareStoreWithTestBlocks(t, dir, bkt, false, NewChunksLimiterFactory(testData.maxChunksLimit), NewSeriesLimiterFactory(testData.maxSeriesLimit), NewBytesLimiterFactory(units.Base2Bytes(testData.maxBytesLimit)), emptyRelabelConfig, allowAllFilterConf) 651 testutil.Ok(t, s.store.SyncBlocks(ctx)) 652 653 req := &storepb.SeriesRequest{ 654 Matchers: []storepb.LabelMatcher{ 655 {Type: storepb.LabelMatcher_EQ, Name: "a", Value: "1"}, 656 }, 657 MinTime: minTimeDuration.PrometheusTimestamp(), 658 MaxTime: maxTimeDuration.PrometheusTimestamp(), 659 } 660 661 s.cache.SwapWith(noopCache{}) 662 srv := newStoreSeriesServer(ctx) 663 err := s.store.Series(req, srv) 664 665 if testData.expectedErr == "" { 666 testutil.Ok(t, err) 667 } else { 668 testutil.NotOk(t, err) 669 testutil.Assert(t, strings.Contains(err.Error(), testData.expectedErr)) 670 status, ok := status.FromError(err) 671 testutil.Equals(t, true, ok) 672 testutil.Equals(t, testData.code, status.Code()) 673 } 674 }) 675 } 676 } 677 678 func TestBucketStore_LabelNames_e2e(t *testing.T) { 679 objtesting.ForeachStore(t, func(t *testing.T, bkt objstore.Bucket) { 680 ctx, cancel := context.WithCancel(context.Background()) 681 defer cancel() 682 683 dir := t.TempDir() 684 685 s := prepareStoreWithTestBlocks(t, dir, bkt, false, NewChunksLimiterFactory(0), NewSeriesLimiterFactory(0), NewBytesLimiterFactory(0), emptyRelabelConfig, allowAllFilterConf) 686 s.cache.SwapWith(noopCache{}) 687 688 mint, maxt := s.store.TimeRange() 689 testutil.Equals(t, s.minTime, mint) 690 testutil.Equals(t, s.maxTime, maxt) 691 692 for name, tc := range map[string]struct { 693 req *storepb.LabelNamesRequest 694 expected []string 695 }{ 696 "basic labelNames": { 697 req: &storepb.LabelNamesRequest{ 698 Start: timestamp.FromTime(minTime), 699 End: timestamp.FromTime(maxTime), 700 }, 701 expected: []string{"a", "b", "c", "ext1", "ext2"}, // ext2 is added by the prepareStoreWithTestBlocks function. 702 }, 703 "outside the time range": { 704 req: &storepb.LabelNamesRequest{ 705 Start: timestamp.FromTime(time.Now().Add(-24 * time.Hour)), 706 End: timestamp.FromTime(time.Now().Add(-23 * time.Hour)), 707 }, 708 expected: nil, 709 }, 710 "matcher matching everything": { 711 req: &storepb.LabelNamesRequest{ 712 Start: timestamp.FromTime(minTime), 713 End: timestamp.FromTime(maxTime), 714 Matchers: []storepb.LabelMatcher{ 715 { 716 Type: storepb.LabelMatcher_EQ, 717 Name: "a", 718 Value: "1", 719 }, 720 }, 721 }, 722 expected: []string{"a", "b", "c", "ext1", "ext2"}, 723 }, 724 "b=1 matcher": { 725 req: &storepb.LabelNamesRequest{ 726 Start: timestamp.FromTime(minTime), 727 End: timestamp.FromTime(maxTime), 728 Matchers: []storepb.LabelMatcher{ 729 { 730 Type: storepb.LabelMatcher_EQ, 731 Name: "b", 732 Value: "1", 733 }, 734 }, 735 }, 736 expected: []string{"a", "b", "ext1"}, 737 }, 738 739 "b='' matcher": { 740 req: &storepb.LabelNamesRequest{ 741 Start: timestamp.FromTime(minTime), 742 End: timestamp.FromTime(maxTime), 743 Matchers: []storepb.LabelMatcher{ 744 { 745 Type: storepb.LabelMatcher_EQ, 746 Name: "b", 747 Value: "", 748 }, 749 }, 750 }, 751 expected: []string{"a", "c", "ext2"}, 752 }, 753 "outside the time range, with matcher": { 754 req: &storepb.LabelNamesRequest{ 755 Start: timestamp.FromTime(time.Now().Add(-24 * time.Hour)), 756 End: timestamp.FromTime(time.Now().Add(-23 * time.Hour)), 757 Matchers: []storepb.LabelMatcher{ 758 { 759 Type: storepb.LabelMatcher_EQ, 760 Name: "a", 761 Value: "1", 762 }, 763 }, 764 }, 765 expected: nil, 766 }, 767 } { 768 t.Run(name, func(t *testing.T) { 769 vals, err := s.store.LabelNames(ctx, tc.req) 770 for _, b := range s.store.blocks { 771 waitTimeout(t, &b.pendingReaders, 5*time.Second) 772 } 773 774 testutil.Ok(t, err) 775 776 testutil.Equals(t, tc.expected, vals.Names) 777 }) 778 } 779 }) 780 } 781 782 func TestBucketStore_LabelNames_SeriesLimiter_e2e(t *testing.T) { 783 cases := map[string]struct { 784 maxSeriesLimit uint64 785 expectedErr string 786 code codes.Code 787 }{ 788 "should succeed if the max series limit is not exceeded": { 789 maxSeriesLimit: math.MaxUint64, 790 }, 791 "should fail if the max series limit is exceeded - ResourceExhausted": { 792 expectedErr: "exceeded series limit", 793 maxSeriesLimit: 1, 794 code: codes.ResourceExhausted, 795 }, 796 } 797 798 for testName, testData := range cases { 799 t.Run(testName, func(t *testing.T) { 800 ctx, cancel := context.WithCancel(context.Background()) 801 defer cancel() 802 803 bkt := objstore.NewInMemBucket() 804 dir := t.TempDir() 805 s := prepareStoreWithTestBlocks(t, dir, bkt, false, NewChunksLimiterFactory(0), NewSeriesLimiterFactory(testData.maxSeriesLimit), NewBytesLimiterFactory(0), emptyRelabelConfig, allowAllFilterConf) 806 testutil.Ok(t, s.store.SyncBlocks(ctx)) 807 req := &storepb.LabelNamesRequest{ 808 Matchers: []storepb.LabelMatcher{ 809 {Type: storepb.LabelMatcher_EQ, Name: "a", Value: "1"}, 810 }, 811 Start: minTimeDuration.PrometheusTimestamp(), 812 End: maxTimeDuration.PrometheusTimestamp(), 813 } 814 815 s.cache.SwapWith(noopCache{}) 816 817 _, err := s.store.LabelNames(context.Background(), req) 818 819 if testData.expectedErr == "" { 820 testutil.Ok(t, err) 821 } else { 822 testutil.NotOk(t, err) 823 testutil.Assert(t, strings.Contains(err.Error(), testData.expectedErr)) 824 825 status, ok := status.FromError(err) 826 testutil.Equals(t, true, ok) 827 testutil.Equals(t, testData.code, status.Code()) 828 } 829 }) 830 } 831 } 832 833 func TestBucketStore_LabelValues_e2e(t *testing.T) { 834 objtesting.ForeachStore(t, func(t *testing.T, bkt objstore.Bucket) { 835 ctx, cancel := context.WithCancel(context.Background()) 836 defer cancel() 837 838 dir := t.TempDir() 839 840 s := prepareStoreWithTestBlocks(t, dir, bkt, false, NewChunksLimiterFactory(0), NewSeriesLimiterFactory(0), NewBytesLimiterFactory(0), emptyRelabelConfig, allowAllFilterConf) 841 s.cache.SwapWith(noopCache{}) 842 843 mint, maxt := s.store.TimeRange() 844 testutil.Equals(t, s.minTime, mint) 845 testutil.Equals(t, s.maxTime, maxt) 846 847 for name, tc := range map[string]struct { 848 req *storepb.LabelValuesRequest 849 expected []string 850 }{ 851 "label a": { 852 req: &storepb.LabelValuesRequest{ 853 Label: "a", 854 Start: timestamp.FromTime(minTime), 855 End: timestamp.FromTime(maxTime), 856 }, 857 expected: []string{"1", "2"}, 858 }, 859 "label a, outside time range": { 860 req: &storepb.LabelValuesRequest{ 861 Label: "a", 862 Start: timestamp.FromTime(time.Now().Add(-24 * time.Hour)), 863 End: timestamp.FromTime(time.Now().Add(-23 * time.Hour)), 864 }, 865 expected: nil, 866 }, 867 "label a, a=1": { 868 req: &storepb.LabelValuesRequest{ 869 Label: "a", 870 Start: timestamp.FromTime(minTime), 871 End: timestamp.FromTime(maxTime), 872 Matchers: []storepb.LabelMatcher{ 873 { 874 Type: storepb.LabelMatcher_EQ, 875 Name: "a", 876 Value: "1", 877 }, 878 }, 879 }, 880 expected: []string{"1"}, 881 }, 882 "label a, a=2, c=2": { 883 req: &storepb.LabelValuesRequest{ 884 Label: "a", 885 Start: timestamp.FromTime(minTime), 886 End: timestamp.FromTime(maxTime), 887 Matchers: []storepb.LabelMatcher{ 888 { 889 Type: storepb.LabelMatcher_EQ, 890 Name: "a", 891 Value: "2", 892 }, 893 { 894 Type: storepb.LabelMatcher_EQ, 895 Name: "c", 896 Value: "2", 897 }, 898 }, 899 }, 900 expected: []string{"2"}, 901 }, 902 "label ext1": { 903 req: &storepb.LabelValuesRequest{ 904 Label: "ext1", 905 Start: timestamp.FromTime(minTime), 906 End: timestamp.FromTime(maxTime), 907 }, 908 expected: []string{"value1"}, 909 }, 910 "label ext1, c=1": { 911 req: &storepb.LabelValuesRequest{ 912 Label: "ext1", 913 Start: timestamp.FromTime(minTime), 914 End: timestamp.FromTime(maxTime), 915 Matchers: []storepb.LabelMatcher{ 916 { 917 Type: storepb.LabelMatcher_EQ, 918 Name: "c", 919 Value: "1", 920 }, 921 }, 922 }, 923 expected: nil, // ext1 is replaced with ext2 for series with c 924 }, 925 } { 926 t.Run(name, func(t *testing.T) { 927 vals, err := s.store.LabelValues(ctx, tc.req) 928 for _, b := range s.store.blocks { 929 waitTimeout(t, &b.pendingReaders, 5*time.Second) 930 } 931 932 testutil.Ok(t, err) 933 934 testutil.Equals(t, tc.expected, emptyToNil(vals.Values)) 935 }) 936 } 937 }) 938 } 939 940 func TestBucketStore_LabelValues_SeriesLimiter_e2e(t *testing.T) { 941 cases := map[string]struct { 942 maxSeriesLimit uint64 943 expectedErr string 944 code codes.Code 945 }{ 946 "should succeed if the max chunks limit is not exceeded": { 947 maxSeriesLimit: math.MaxUint64, 948 }, 949 "should fail if the max series limit is exceeded - ResourceExhausted": { 950 expectedErr: "exceeded series limit", 951 maxSeriesLimit: 1, 952 code: codes.ResourceExhausted, 953 }, 954 } 955 956 for testName, testData := range cases { 957 t.Run(testName, func(t *testing.T) { 958 ctx, cancel := context.WithCancel(context.Background()) 959 defer cancel() 960 bkt := objstore.NewInMemBucket() 961 962 dir := t.TempDir() 963 964 s := prepareStoreWithTestBlocks(t, dir, bkt, false, NewChunksLimiterFactory(0), NewSeriesLimiterFactory(testData.maxSeriesLimit), NewBytesLimiterFactory(0), emptyRelabelConfig, allowAllFilterConf) 965 testutil.Ok(t, s.store.SyncBlocks(ctx)) 966 967 req := &storepb.LabelValuesRequest{ 968 Label: "a", 969 Start: minTimeDuration.PrometheusTimestamp(), 970 End: maxTimeDuration.PrometheusTimestamp(), 971 Matchers: []storepb.LabelMatcher{ 972 { 973 Type: storepb.LabelMatcher_EQ, 974 Name: "a", 975 Value: "1", 976 }, 977 }, 978 } 979 980 s.cache.SwapWith(noopCache{}) 981 982 _, err := s.store.LabelValues(context.Background(), req) 983 984 if testData.expectedErr == "" { 985 testutil.Ok(t, err) 986 } else { 987 testutil.NotOk(t, err) 988 testutil.Assert(t, strings.Contains(err.Error(), testData.expectedErr)) 989 990 status, ok := status.FromError(err) 991 testutil.Equals(t, true, ok) 992 testutil.Equals(t, testData.code, status.Code()) 993 } 994 }) 995 } 996 } 997 998 func emptyToNil(values []string) []string { 999 if len(values) == 0 { 1000 return nil 1001 } 1002 return values 1003 } 1004 1005 func waitTimeout(t *testing.T, wg *sync.WaitGroup, timeout time.Duration) { 1006 c := make(chan struct{}) 1007 go func() { 1008 defer close(c) 1009 wg.Wait() 1010 }() 1011 select { 1012 case <-c: 1013 return 1014 case <-time.After(timeout): 1015 t.Fatalf("timeout waiting wg for %v", timeout) 1016 } 1017 }