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 }