github.com/grafana/pyroscope@v1.18.0/pkg/storegateway/bucket_index_metadata_fetcher_test.go (about)

     1  // SPDX-License-Identifier: AGPL-3.0-only
     2  // Provenance-includes-location: https://github.com/cortexproject/cortex/blob/master/pkg/storegateway/bucket_index_metadata_fetcher_test.go
     3  // Provenance-includes-license: Apache-2.0
     4  // Provenance-includes-copyright: The Cortex Authors.
     5  
     6  package storegateway
     7  
     8  import (
     9  	"bytes"
    10  	"context"
    11  	"path"
    12  	"strings"
    13  	"testing"
    14  	"time"
    15  
    16  	"github.com/go-kit/log"
    17  	"github.com/grafana/dskit/concurrency"
    18  	"github.com/oklog/ulid/v2"
    19  	"github.com/prometheus/client_golang/prometheus"
    20  	"github.com/prometheus/client_golang/prometheus/testutil"
    21  	"github.com/prometheus/common/model"
    22  	"github.com/stretchr/testify/assert"
    23  	"github.com/stretchr/testify/require"
    24  
    25  	"github.com/grafana/pyroscope/pkg/objstore"
    26  	objstore_testutil "github.com/grafana/pyroscope/pkg/objstore/testutil"
    27  	"github.com/grafana/pyroscope/pkg/phlaredb/block"
    28  	"github.com/grafana/pyroscope/pkg/phlaredb/bucketindex"
    29  )
    30  
    31  func TestBucketIndexMetadataFetcher_Fetch(t *testing.T) {
    32  	const userID = "user-1"
    33  
    34  	ctx := context.Background()
    35  
    36  	bkt, _ := objstore_testutil.NewFilesystemBucket(t, ctx, t.TempDir())
    37  	reg := prometheus.NewPedanticRegistry()
    38  	now := time.Now()
    39  	logs := &concurrency.SyncBuffer{}
    40  	logger := log.NewLogfmtLogger(logs)
    41  
    42  	// Create a bucket index.
    43  	block1 := &bucketindex.Block{ID: ulid.MustNew(1, nil)}
    44  	block2 := &bucketindex.Block{ID: ulid.MustNew(2, nil)}
    45  	block3 := &bucketindex.Block{ID: ulid.MustNew(3, nil)}
    46  	block4 := &bucketindex.Block{ID: ulid.MustNew(4, nil), MinTime: model.Time(now.Add(-30 * time.Minute).UnixMilli())} // Has most-recent data, to be ignored by minTimeMetaFilter.
    47  
    48  	mark1 := &bucketindex.BlockDeletionMark{ID: block1.ID, DeletionTime: now.Add(-time.Hour).Unix()}     // Below the ignore delay threshold.
    49  	mark2 := &bucketindex.BlockDeletionMark{ID: block2.ID, DeletionTime: now.Add(-3 * time.Hour).Unix()} // Above the ignore delay threshold.
    50  
    51  	require.NoError(t, bucketindex.WriteIndex(ctx, bkt, userID, nil, &bucketindex.Index{
    52  		Version:            bucketindex.IndexVersion1,
    53  		Blocks:             bucketindex.Blocks{block1, block2, block3, block4},
    54  		BlockDeletionMarks: bucketindex.BlockDeletionMarks{mark1, mark2},
    55  		UpdatedAt:          now.Unix(),
    56  	}))
    57  
    58  	// Create a metadata fetcher with filters.
    59  	filters := []block.MetadataFilter{
    60  		NewIgnoreDeletionMarkFilter(logger, objstore.NewTenantBucketClient(userID, bkt, nil), 2*time.Hour, 1),
    61  		newMinTimeMetaFilter(1 * time.Hour),
    62  	}
    63  
    64  	fetcher := NewBucketIndexMetadataFetcher(userID, bkt, nil, logger, reg, filters)
    65  	metas, partials, err := fetcher.Fetch(ctx)
    66  	require.NoError(t, err)
    67  	assert.Equal(t, map[ulid.ULID]*block.Meta{
    68  		block1.ID: block1.Meta(),
    69  		block3.ID: block3.Meta(),
    70  	}, metas)
    71  	assert.Empty(t, partials)
    72  	assert.Empty(t, logs)
    73  
    74  	assert.NoError(t, testutil.GatherAndCompare(reg, bytes.NewBufferString(`
    75  		# HELP blocks_meta_sync_failures_total Total blocks metadata synchronization failures
    76  		# TYPE blocks_meta_sync_failures_total counter
    77  		blocks_meta_sync_failures_total 0
    78  
    79  		# HELP blocks_meta_synced Number of block metadata synced
    80  		# TYPE blocks_meta_synced gauge
    81  		blocks_meta_synced{state="corrupted-bucket-index"} 0
    82  		blocks_meta_synced{state="corrupted-meta-json"} 0
    83  		blocks_meta_synced{state="duplicate"} 0
    84  		blocks_meta_synced{state="failed"} 0
    85  		blocks_meta_synced{state="label-excluded"} 0
    86  		blocks_meta_synced{state="loaded"} 2
    87  		blocks_meta_synced{state="marked-for-deletion"} 1
    88  		blocks_meta_synced{state="marked-for-no-compact"} 0
    89  		blocks_meta_synced{state="no-bucket-index"} 0
    90  		blocks_meta_synced{state="no-meta-json"} 0
    91  		blocks_meta_synced{state="time-excluded"} 0
    92  		blocks_meta_synced{state="min-time-excluded"} 1
    93  
    94  		# HELP blocks_meta_syncs_total Total blocks metadata synchronization attempts
    95  		# TYPE blocks_meta_syncs_total counter
    96  		blocks_meta_syncs_total 1
    97  	`),
    98  		"blocks_meta_sync_failures_total",
    99  		"blocks_meta_synced",
   100  		"blocks_meta_syncs_total",
   101  	))
   102  }
   103  
   104  func TestBucketIndexMetadataFetcher_Fetch_NoBucketIndex(t *testing.T) {
   105  	const userID = "user-1"
   106  
   107  	ctx := context.Background()
   108  	bkt, _ := objstore_testutil.NewFilesystemBucket(t, ctx, t.TempDir())
   109  	reg := prometheus.NewPedanticRegistry()
   110  	logs := &concurrency.SyncBuffer{}
   111  	logger := log.NewLogfmtLogger(logs)
   112  
   113  	fetcher := NewBucketIndexMetadataFetcher(userID, bkt, nil, logger, reg, nil)
   114  	metas, partials, err := fetcher.Fetch(ctx)
   115  	require.NoError(t, err)
   116  	assert.Empty(t, metas)
   117  	assert.Empty(t, partials)
   118  	assert.Contains(t, logs.String(), "no bucket index found, falling back to fetching directly from bucket")
   119  
   120  	assert.NoError(t, testutil.GatherAndCompare(reg, bytes.NewBufferString(`
   121  		# HELP blocks_meta_sync_failures_total Total blocks metadata synchronization failures
   122  		# TYPE blocks_meta_sync_failures_total counter
   123  		blocks_meta_sync_failures_total 0
   124  
   125  		# HELP blocks_meta_synced Number of block metadata synced
   126  		# TYPE blocks_meta_synced gauge
   127  		blocks_meta_synced{state="corrupted-bucket-index"} 0
   128  		blocks_meta_synced{state="corrupted-meta-json"} 0
   129  		blocks_meta_synced{state="duplicate"} 0
   130  		blocks_meta_synced{state="failed"} 0
   131  		blocks_meta_synced{state="label-excluded"} 0
   132  		blocks_meta_synced{state="loaded"} 0
   133  		blocks_meta_synced{state="marked-for-deletion"} 0
   134  		blocks_meta_synced{state="marked-for-no-compact"} 0
   135  		blocks_meta_synced{state="no-bucket-index"} 1
   136  		blocks_meta_synced{state="no-meta-json"} 0
   137  		blocks_meta_synced{state="time-excluded"} 0
   138  		blocks_meta_synced{state="min-time-excluded"} 0
   139  
   140  		# HELP blocks_meta_syncs_total Total blocks metadata synchronization attempts
   141  		# TYPE blocks_meta_syncs_total counter
   142  		blocks_meta_syncs_total 1
   143  	`),
   144  		"blocks_meta_sync_failures_total",
   145  		"blocks_meta_synced",
   146  		"blocks_meta_syncs_total",
   147  	))
   148  }
   149  
   150  func TestBucketIndexMetadataFetcher_Fetch_CorruptedBucketIndex(t *testing.T) {
   151  	const userID = "user-1"
   152  
   153  	ctx := context.Background()
   154  
   155  	bkt, _ := objstore_testutil.NewFilesystemBucket(t, ctx, t.TempDir())
   156  	reg := prometheus.NewPedanticRegistry()
   157  	logs := &concurrency.SyncBuffer{}
   158  	logger := log.NewLogfmtLogger(logs)
   159  
   160  	// Upload a corrupted bucket index.
   161  	require.NoError(t, bkt.Upload(ctx, path.Join(userID, "phlaredb/", bucketindex.IndexCompressedFilename), strings.NewReader("invalid}!")))
   162  
   163  	fetcher := NewBucketIndexMetadataFetcher(userID, bkt, nil, logger, reg, nil)
   164  	metas, partials, err := fetcher.Fetch(ctx)
   165  	require.NoError(t, err)
   166  	assert.Empty(t, metas)
   167  	assert.Empty(t, partials)
   168  	assert.Regexp(t, "corrupted bucket index found", logs)
   169  
   170  	assert.NoError(t, testutil.GatherAndCompare(reg, bytes.NewBufferString(`
   171  		# HELP blocks_meta_sync_failures_total Total blocks metadata synchronization failures
   172  		# TYPE blocks_meta_sync_failures_total counter
   173  		blocks_meta_sync_failures_total 0
   174  
   175  		# HELP blocks_meta_synced Number of block metadata synced
   176  		# TYPE blocks_meta_synced gauge
   177  		blocks_meta_synced{state="corrupted-bucket-index"} 1
   178  		blocks_meta_synced{state="corrupted-meta-json"} 0
   179  		blocks_meta_synced{state="duplicate"} 0
   180  		blocks_meta_synced{state="failed"} 0
   181  		blocks_meta_synced{state="label-excluded"} 0
   182  		blocks_meta_synced{state="loaded"} 0
   183  		blocks_meta_synced{state="marked-for-deletion"} 0
   184  		blocks_meta_synced{state="marked-for-no-compact"} 0
   185  		blocks_meta_synced{state="no-bucket-index"} 0
   186  		blocks_meta_synced{state="no-meta-json"} 0
   187  		blocks_meta_synced{state="time-excluded"} 0
   188  		blocks_meta_synced{state="min-time-excluded"} 0
   189  
   190  		# HELP blocks_meta_syncs_total Total blocks metadata synchronization attempts
   191  		# TYPE blocks_meta_syncs_total counter
   192  		blocks_meta_syncs_total 1
   193  	`),
   194  		"blocks_meta_sync_failures_total",
   195  		"blocks_meta_synced",
   196  		"blocks_meta_syncs_total",
   197  	))
   198  }