github.com/yankunsam/loki/v2@v2.6.3-0.20220817130409-389df5235c27/pkg/storage/stores/shipper/testutil/testutil.go (about)

     1  package testutil
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"io"
     7  	"os"
     8  	"path/filepath"
     9  	"strconv"
    10  	"sync"
    11  	"testing"
    12  
    13  	"github.com/grafana/dskit/tenant"
    14  	"github.com/klauspost/compress/gzip"
    15  	"github.com/stretchr/testify/require"
    16  	"github.com/weaveworks/common/user"
    17  	"go.etcd.io/bbolt"
    18  
    19  	"github.com/grafana/loki/pkg/storage/chunk/client/local"
    20  	chunk_util "github.com/grafana/loki/pkg/storage/chunk/client/util"
    21  	"github.com/grafana/loki/pkg/storage/stores/series/index"
    22  )
    23  
    24  func AddRecordsToDB(t testing.TB, path string, start, numRecords int, bucketName []byte) {
    25  	t.Helper()
    26  	db, err := local.OpenBoltdbFile(path)
    27  	require.NoError(t, err)
    28  
    29  	batch := local.NewWriteBatch()
    30  	AddRecordsToBatch(batch, "test", start, numRecords)
    31  
    32  	if len(bucketName) == 0 {
    33  		bucketName = local.IndexBucketName
    34  	}
    35  
    36  	require.NoError(t, local.WriteToDB(context.Background(), db, bucketName, batch.(*local.BoltWriteBatch).Writes["test"]))
    37  
    38  	require.NoError(t, db.Sync())
    39  	require.NoError(t, db.Close())
    40  }
    41  
    42  func AddRecordsToBatch(batch index.WriteBatch, tableName string, start, numRecords int) {
    43  	for i := 0; i < numRecords; i++ {
    44  		rec := []byte(strconv.Itoa(start + i))
    45  		batch.Add(tableName, "", rec, rec)
    46  	}
    47  }
    48  
    49  // nolint
    50  func queryIndexes(t *testing.T, ctx context.Context, queries []index.Query, indexIteratorFunc IndexIteratorFunc, callback index.QueryPagesCallback) {
    51  	userID, err := tenant.TenantID(ctx)
    52  	require.NoError(t, err)
    53  
    54  	for _, query := range queries {
    55  		err := indexIteratorFunc(ctx, query.TableName, func(boltdb *bbolt.DB) error {
    56  			return queryBoltDB(ctx, boltdb, []byte(userID), []index.Query{query}, callback)
    57  		})
    58  		require.NoError(t, err)
    59  	}
    60  }
    61  
    62  type IndexIteratorFunc func(ctx context.Context, table string, callback func(boltdb *bbolt.DB) error) error
    63  
    64  func VerifyIndexes(t *testing.T, userID string, queries []index.Query, indexIteratorFunc IndexIteratorFunc, start, numRecords int) {
    65  	t.Helper()
    66  	minValue := start
    67  	maxValue := start + numRecords
    68  	fetchedRecords := make(map[string]string)
    69  
    70  	queryIndexes(t, user.InjectOrgID(context.Background(), userID), queries, indexIteratorFunc, makeTestCallback(t, minValue, maxValue, fetchedRecords))
    71  	require.Len(t, fetchedRecords, numRecords)
    72  }
    73  
    74  type SingleDBQuerier interface {
    75  	QueryDB(ctx context.Context, db *bbolt.DB, bucketName []byte, query index.Query, callback index.QueryPagesCallback) error
    76  }
    77  
    78  func VerifySingleIndexFile(t *testing.T, query index.Query, db *bbolt.DB, bucketName []byte, start, numRecords int) {
    79  	t.Helper()
    80  	minValue := start
    81  	maxValue := start + numRecords
    82  	fetchedRecords := make(map[string]string)
    83  
    84  	err := db.View(func(tx *bbolt.Tx) error {
    85  		b := tx.Bucket(bucketName)
    86  		require.NotNil(t, b)
    87  		return local.QueryWithCursor(context.Background(), b.Cursor(), query, makeTestCallback(t, minValue, maxValue, fetchedRecords))
    88  	})
    89  
    90  	require.NoError(t, err)
    91  	require.Len(t, fetchedRecords, numRecords)
    92  }
    93  
    94  func makeTestCallback(t *testing.T, minValue, maxValue int, records map[string]string) index.QueryPagesCallback {
    95  	t.Helper()
    96  	recordsMtx := sync.Mutex{}
    97  	return func(query index.Query, batch index.ReadBatchResult) (shouldContinue bool) {
    98  		itr := batch.Iterator()
    99  		for itr.Next() {
   100  			require.Equal(t, itr.RangeValue(), itr.Value())
   101  			rec, err := strconv.Atoi(string(itr.Value()))
   102  
   103  			require.NoError(t, err)
   104  			require.GreaterOrEqual(t, rec, minValue)
   105  			require.LessOrEqual(t, rec, maxValue)
   106  
   107  			recordsMtx.Lock()
   108  			records[string(itr.RangeValue())] = string(itr.Value())
   109  			recordsMtx.Unlock()
   110  		}
   111  		return true
   112  	}
   113  }
   114  
   115  // ToDo(Sandeep): refactor to remove DBConfig and use DBRecords directly
   116  type DBConfig struct {
   117  	DBRecords
   118  }
   119  
   120  type DBRecords struct {
   121  	Start, NumRecords int
   122  }
   123  
   124  func SetupDBsAtPath(t *testing.T, path string, dbs map[string]DBConfig, bucketName []byte) string {
   125  	t.Helper()
   126  	boltIndexClient, err := local.NewBoltDBIndexClient(local.BoltDBConfig{Directory: path})
   127  	require.NoError(t, err)
   128  
   129  	defer boltIndexClient.Stop()
   130  
   131  	require.NoError(t, chunk_util.EnsureDirectory(path))
   132  
   133  	for name, dbConfig := range dbs {
   134  		AddRecordsToDB(t, filepath.Join(path, name), dbConfig.Start, dbConfig.NumRecords, bucketName)
   135  	}
   136  
   137  	return path
   138  }
   139  
   140  func DecompressFile(t *testing.T, src, dest string) {
   141  	t.Helper()
   142  	// open compressed file from storage
   143  	compressedFile, err := os.Open(src)
   144  	require.NoError(t, err)
   145  
   146  	// get a compressed reader
   147  	compressedReader, err := gzip.NewReader(compressedFile)
   148  	require.NoError(t, err)
   149  
   150  	decompressedFile, err := os.Create(dest)
   151  	require.NoError(t, err)
   152  
   153  	// do the decompression
   154  	_, err = io.Copy(decompressedFile, compressedReader)
   155  	require.NoError(t, err)
   156  
   157  	// close the references
   158  	require.NoError(t, compressedFile.Close())
   159  	require.NoError(t, decompressedFile.Close())
   160  }
   161  
   162  type DBsConfig struct {
   163  	DBRecordsStart                     int
   164  	NumUnCompactedDBs, NumCompactedDBs int
   165  }
   166  
   167  func (c DBsConfig) String() string {
   168  	return fmt.Sprintf("Default Bucket DBs - UCDBs: %d, CDBs: %d", c.NumUnCompactedDBs, c.NumCompactedDBs)
   169  }
   170  
   171  type PerUserDBsConfig struct {
   172  	DBsConfig
   173  	NumUsers int
   174  }
   175  
   176  func (c PerUserDBsConfig) String() string {
   177  	return fmt.Sprintf("Per User DBs - UCDBs: %d, CDBs: %d, Users: %d", c.NumUnCompactedDBs, c.NumCompactedDBs, c.NumUsers)
   178  }
   179  
   180  func SetupTable(t *testing.T, path string, commonDBsConfig DBsConfig, perUserDBsConfig PerUserDBsConfig) {
   181  	numRecordsPerDB := 100
   182  
   183  	commonDBsWithDefaultBucket := map[string]DBConfig{}
   184  	commonDBsWithPerUserBucket := map[string]map[string]DBConfig{}
   185  	perUserDBs := map[string]map[string]DBConfig{}
   186  
   187  	for i := 0; i < commonDBsConfig.NumUnCompactedDBs; i++ {
   188  		commonDBsWithDefaultBucket[fmt.Sprint(i)] = DBConfig{
   189  			DBRecords: DBRecords{
   190  				Start:      commonDBsConfig.DBRecordsStart + i*numRecordsPerDB,
   191  				NumRecords: numRecordsPerDB,
   192  			},
   193  		}
   194  	}
   195  
   196  	for i := 0; i < commonDBsConfig.NumCompactedDBs; i++ {
   197  		commonDBsWithDefaultBucket[fmt.Sprintf("compactor-%d", i)] = DBConfig{
   198  			DBRecords: DBRecords{
   199  				Start:      commonDBsConfig.DBRecordsStart + i*numRecordsPerDB,
   200  				NumRecords: ((i + 1) * numRecordsPerDB) * 2,
   201  			},
   202  		}
   203  	}
   204  
   205  	for i := 0; i < perUserDBsConfig.NumUnCompactedDBs; i++ {
   206  		dbName := fmt.Sprintf("per-user-bucket-db-%d", i)
   207  		commonDBsWithPerUserBucket[dbName] = map[string]DBConfig{}
   208  		for j := 0; j < perUserDBsConfig.NumUsers; j++ {
   209  			commonDBsWithPerUserBucket[dbName][BuildUserID(j)] = DBConfig{
   210  				DBRecords: DBRecords{
   211  					Start:      perUserDBsConfig.DBRecordsStart + i*numRecordsPerDB,
   212  					NumRecords: numRecordsPerDB,
   213  				},
   214  			}
   215  		}
   216  	}
   217  
   218  	for i := 0; i < perUserDBsConfig.NumCompactedDBs; i++ {
   219  		for j := 0; j < perUserDBsConfig.NumUsers; j++ {
   220  			userID := BuildUserID(j)
   221  			if i == 0 {
   222  				perUserDBs[userID] = map[string]DBConfig{}
   223  			}
   224  			perUserDBs[userID][fmt.Sprintf("compactor-%d", i)] = DBConfig{
   225  				DBRecords: DBRecords{
   226  					Start:      perUserDBsConfig.DBRecordsStart + i*numRecordsPerDB,
   227  					NumRecords: (i + 1) * numRecordsPerDB,
   228  				},
   229  			}
   230  		}
   231  	}
   232  
   233  	SetupDBsAtPath(t, path, commonDBsWithDefaultBucket, local.IndexBucketName)
   234  
   235  	for dbName, userRecords := range commonDBsWithPerUserBucket {
   236  		for userID, dbConfig := range userRecords {
   237  			SetupDBsAtPath(t, path, map[string]DBConfig{
   238  				dbName: dbConfig,
   239  			}, []byte(userID))
   240  		}
   241  	}
   242  
   243  	for userID, dbRecords := range perUserDBs {
   244  		SetupDBsAtPath(t, filepath.Join(path, userID), dbRecords, local.IndexBucketName)
   245  	}
   246  }
   247  
   248  func BuildUserID(id int) string {
   249  	return fmt.Sprintf("user-%d", id)
   250  }
   251  
   252  func queryBoltDB(ctx context.Context, db *bbolt.DB, userID []byte, queries []index.Query, callback index.QueryPagesCallback) error {
   253  	return db.View(func(tx *bbolt.Tx) error {
   254  		bucket := tx.Bucket(userID)
   255  		if bucket == nil {
   256  			bucket = tx.Bucket(local.IndexBucketName)
   257  			if bucket == nil {
   258  				return nil
   259  			}
   260  		}
   261  
   262  		for _, query := range queries {
   263  			if err := local.QueryWithCursor(ctx, bucket.Cursor(), query, callback); err != nil {
   264  				return err
   265  			}
   266  		}
   267  		return nil
   268  	})
   269  }