@@ -3,6 +3,7 @@ package bucketindex
33import (
44 "bytes"
55 "context"
6+ "encoding/json"
67 "path"
78 "strings"
89 "testing"
@@ -21,6 +22,7 @@ import (
2122 "github.com/thanos-io/thanos/pkg/block/metadata"
2223
2324 "github.com/cortexproject/cortex/pkg/storage/bucket"
25+ "github.com/cortexproject/cortex/pkg/storage/parquet"
2426 "github.com/cortexproject/cortex/pkg/storage/tsdb/testutil"
2527)
2628
@@ -301,6 +303,150 @@ func TestUpdater_UpdateIndex_NoTenantInTheBucket(t *testing.T) {
301303 }
302304}
303305
306+ func TestUpdater_UpdateIndex_WithParquet (t * testing.T ) {
307+ const userID = "user-1"
308+
309+ bkt , _ := testutil .PrepareFilesystemBucket (t )
310+
311+ ctx := context .Background ()
312+ logger := log .NewNopLogger ()
313+
314+ // Generate the initial index.
315+ bkt = BucketWithGlobalMarkers (bkt )
316+ block1 := testutil .MockStorageBlock (t , bkt , userID , 10 , 20 )
317+ block2 := testutil .MockStorageBlock (t , bkt , userID , 20 , 30 )
318+ block2Mark := testutil .MockStorageDeletionMark (t , bkt , userID , block2 )
319+ // Add parquet marker to block 1.
320+ block1ParquetMark := testutil .MockStorageParquetConverterMark (t , bkt , userID , block1 )
321+
322+ w := NewUpdater (bkt , userID , nil , logger ).EnableParquet ()
323+ returnedIdx , _ , _ , err := w .UpdateIndex (ctx , nil )
324+ require .NoError (t , err )
325+ assertBucketIndexEqualWithParquet (t , returnedIdx , bkt , userID ,
326+ []tsdb.BlockMeta {block1 , block2 },
327+ []* metadata.DeletionMark {block2Mark }, map [string ]* parquet.ConverterMarkMeta {
328+ block1 .ULID .String (): {Version : block1ParquetMark .Version },
329+ })
330+
331+ // Create new blocks, and update the index.
332+ block3 := testutil .MockStorageBlock (t , bkt , userID , 30 , 40 )
333+ block4 := testutil .MockStorageBlock (t , bkt , userID , 40 , 50 )
334+ block4Mark := testutil .MockStorageDeletionMark (t , bkt , userID , block4 )
335+
336+ returnedIdx , _ , _ , err = w .UpdateIndex (ctx , returnedIdx )
337+ require .NoError (t , err )
338+ assertBucketIndexEqualWithParquet (t , returnedIdx , bkt , userID ,
339+ []tsdb.BlockMeta {block1 , block2 , block3 , block4 },
340+ []* metadata.DeletionMark {block2Mark , block4Mark },
341+ map [string ]* parquet.ConverterMarkMeta {
342+ block1 .ULID .String (): {Version : block1ParquetMark .Version },
343+ })
344+
345+ // Hard delete a block and update the index.
346+ require .NoError (t , block .Delete (ctx , log .NewNopLogger (), bucket .NewUserBucketClient (userID , bkt , nil ), block2 .ULID ))
347+
348+ returnedIdx , _ , _ , err = w .UpdateIndex (ctx , returnedIdx )
349+ require .NoError (t , err )
350+ assertBucketIndexEqualWithParquet (t , returnedIdx , bkt , userID ,
351+ []tsdb.BlockMeta {block1 , block3 , block4 },
352+ []* metadata.DeletionMark {block4Mark }, map [string ]* parquet.ConverterMarkMeta {
353+ block1 .ULID .String (): {Version : block1ParquetMark .Version },
354+ })
355+
356+ // Upload parquet marker to an old block and update index
357+ block3ParquetMark := testutil .MockStorageParquetConverterMark (t , bkt , userID , block3 )
358+ returnedIdx , _ , _ , err = w .UpdateIndex (ctx , returnedIdx )
359+ require .NoError (t , err )
360+ assertBucketIndexEqualWithParquet (t , returnedIdx , bkt , userID ,
361+ []tsdb.BlockMeta {block1 , block3 , block4 },
362+ []* metadata.DeletionMark {block4Mark }, map [string ]* parquet.ConverterMarkMeta {
363+ block1 .ULID .String (): {Version : block1ParquetMark .Version },
364+ block3 .ULID .String (): {Version : block3ParquetMark .Version },
365+ })
366+ }
367+
368+ func TestUpdater_UpdateParquetBlockIndexEntry (t * testing.T ) {
369+ const userID = "user-1"
370+ ctx := context .Background ()
371+ logger := log .NewNopLogger ()
372+
373+ tests := []struct {
374+ name string
375+ setupBucket func (t * testing.T , bkt objstore.InstrumentedBucket , blockID ulid.ULID ) objstore.InstrumentedBucket
376+ expectedError error
377+ expectParquet bool
378+ expectParquetMeta * parquet.ConverterMarkMeta
379+ }{
380+ {
381+ name : "should successfully read parquet marker" ,
382+ setupBucket : func (t * testing.T , bkt objstore.InstrumentedBucket , blockID ulid.ULID ) objstore.InstrumentedBucket {
383+ parquetMark := parquet.ConverterMarkMeta {
384+ Version : 1 ,
385+ }
386+ data , err := json .Marshal (parquetMark )
387+ require .NoError (t , err )
388+ require .NoError (t , bkt .Upload (ctx , path .Join (userID , blockID .String (), parquet .ConverterMarkerFileName ), bytes .NewReader (data )))
389+ return bkt
390+ },
391+ expectedError : nil ,
392+ expectParquet : true ,
393+ expectParquetMeta : & parquet.ConverterMarkMeta {Version : 1 },
394+ },
395+ {
396+ name : "should handle missing parquet marker" ,
397+ setupBucket : func (t * testing.T , bkt objstore.InstrumentedBucket , blockID ulid.ULID ) objstore.InstrumentedBucket {
398+ // Don't upload any parquet marker
399+ return bkt
400+ },
401+ expectedError : nil ,
402+ expectParquet : false ,
403+ },
404+ {
405+ name : "should handle access denied" ,
406+ setupBucket : func (t * testing.T , bkt objstore.InstrumentedBucket , blockID ulid.ULID ) objstore.InstrumentedBucket {
407+ return & testutil.MockBucketFailure {
408+ Bucket : bkt ,
409+ GetFailures : map [string ]error {
410+ path .Join (userID , blockID .String (), parquet .ConverterMarkerFileName ): testutil .ErrKeyAccessDeniedError ,
411+ },
412+ }
413+ },
414+ expectedError : nil ,
415+ expectParquet : false ,
416+ },
417+ }
418+
419+ for _ , tc := range tests {
420+ t .Run (tc .name , func (t * testing.T ) {
421+ bkt , _ := testutil .PrepareFilesystemBucket (t )
422+ blockID := ulid .MustNew (1 , nil )
423+ block := & Block {ID : blockID }
424+
425+ // Setup the bucket with test data
426+ bkt = tc .setupBucket (t , bkt , blockID )
427+
428+ // Create an instrumented bucket wrapper
429+ registry := prometheus .NewRegistry ()
430+ ibkt := objstore .WrapWithMetrics (bkt , prometheus .WrapRegistererWithPrefix ("thanos_" , registry ), "test-bucket" )
431+ w := NewUpdater (ibkt , userID , nil , logger )
432+
433+ err := w .updateParquetBlockIndexEntry (ctx , blockID , block )
434+ if tc .expectedError != nil {
435+ assert .True (t , errors .Is (err , tc .expectedError ))
436+ } else {
437+ assert .NoError (t , err )
438+ }
439+
440+ if tc .expectParquet {
441+ assert .NotNil (t , block .Parquet )
442+ assert .Equal (t , tc .expectParquetMeta , block .Parquet )
443+ } else {
444+ assert .Nil (t , block .Parquet )
445+ }
446+ })
447+ }
448+ }
449+
304450func getBlockUploadedAt (t testing.TB , bkt objstore.Bucket , userID string , blockID ulid.ULID ) int64 {
305451 metaFile := path .Join (userID , blockID .String (), block .MetaFilename )
306452
@@ -338,3 +484,36 @@ func assertBucketIndexEqual(t testing.TB, idx *Index, bkt objstore.Bucket, userI
338484
339485 assert .ElementsMatch (t , expectedMarkEntries , idx .BlockDeletionMarks )
340486}
487+
488+ func assertBucketIndexEqualWithParquet (t testing.TB , idx * Index , bkt objstore.Bucket , userID string , expectedBlocks []tsdb.BlockMeta , expectedDeletionMarks []* metadata.DeletionMark , parquetBlocks map [string ]* parquet.ConverterMarkMeta ) {
489+ assert .Equal (t , IndexVersion1 , idx .Version )
490+ assert .InDelta (t , time .Now ().Unix (), idx .UpdatedAt , 2 )
491+
492+ // Build the list of expected block index entries.
493+ var expectedBlockEntries []* Block
494+ for _ , b := range expectedBlocks {
495+ block := & Block {
496+ ID : b .ULID ,
497+ MinTime : b .MinTime ,
498+ MaxTime : b .MaxTime ,
499+ UploadedAt : getBlockUploadedAt (t , bkt , userID , b .ULID ),
500+ }
501+ if meta , ok := parquetBlocks [b .ULID .String ()]; ok {
502+ block .Parquet = meta
503+ }
504+ expectedBlockEntries = append (expectedBlockEntries , block )
505+ }
506+
507+ assert .ElementsMatch (t , expectedBlockEntries , idx .Blocks )
508+
509+ // Build the list of expected block deletion mark index entries.
510+ var expectedMarkEntries []* BlockDeletionMark
511+ for _ , m := range expectedDeletionMarks {
512+ expectedMarkEntries = append (expectedMarkEntries , & BlockDeletionMark {
513+ ID : m .ID ,
514+ DeletionTime : m .DeletionTime ,
515+ })
516+ }
517+
518+ assert .ElementsMatch (t , expectedMarkEntries , idx .BlockDeletionMarks )
519+ }
0 commit comments