github.com/yankunsam/loki/v2@v2.6.3-0.20220817130409-389df5235c27/pkg/storage/chunk/client/local/boltdb_index_client_test.go (about) 1 package local 2 3 import ( 4 "context" 5 "fmt" 6 "os" 7 "path/filepath" 8 "testing" 9 10 "github.com/stretchr/testify/require" 11 "go.etcd.io/bbolt" 12 13 "github.com/grafana/loki/pkg/storage/config" 14 "github.com/grafana/loki/pkg/storage/stores/series/index" 15 ) 16 17 var ( 18 testKey = []byte("test-key") 19 testValue = []byte("test-value") 20 ) 21 22 func setupDB(t *testing.T, boltdbIndexClient *BoltIndexClient, dbname string) { 23 db, err := boltdbIndexClient.GetDB(dbname, DBOperationWrite) 24 require.NoError(t, err) 25 26 err = db.Update(func(tx *bbolt.Tx) error { 27 b, err := tx.CreateBucketIfNotExists(IndexBucketName) 28 if err != nil { 29 return err 30 } 31 32 return b.Put(testKey, testValue) 33 }) 34 require.NoError(t, err) 35 } 36 37 func TestBoltDBReload(t *testing.T) { 38 dirname := t.TempDir() 39 40 boltdbIndexClient, err := NewBoltDBIndexClient(BoltDBConfig{ 41 Directory: dirname, 42 }) 43 require.NoError(t, err) 44 45 defer boltdbIndexClient.Stop() 46 47 testDb1 := "test1" 48 testDb2 := "test2" 49 50 setupDB(t, boltdbIndexClient, testDb1) 51 setupDB(t, boltdbIndexClient, testDb2) 52 53 boltdbIndexClient.reload() 54 require.Equal(t, 2, len(boltdbIndexClient.dbs), "There should be 2 boltdbs open") 55 56 require.NoError(t, os.Remove(filepath.Join(dirname, testDb1))) 57 58 droppedDb, err := boltdbIndexClient.GetDB(testDb1, DBOperationRead) 59 require.NoError(t, err) 60 61 valueFromDb := []byte{} 62 _ = droppedDb.View(func(tx *bbolt.Tx) error { 63 b := tx.Bucket(IndexBucketName) 64 valueFromDb = b.Get(testKey) 65 return nil 66 }) 67 require.Equal(t, testValue, valueFromDb, "should match value from db") 68 69 boltdbIndexClient.reload() 70 71 require.Equal(t, 1, len(boltdbIndexClient.dbs), "There should be 1 boltdb open") 72 73 _, err = boltdbIndexClient.GetDB(testDb1, DBOperationRead) 74 require.Equal(t, ErrUnexistentBoltDB, err) 75 } 76 77 func TestBoltDB_GetDB(t *testing.T) { 78 dirname := t.TempDir() 79 80 boltdbIndexClient, err := NewBoltDBIndexClient(BoltDBConfig{ 81 Directory: dirname, 82 }) 83 require.NoError(t, err) 84 85 // setup a db to already exist 86 testDb1 := "test1" 87 setupDB(t, boltdbIndexClient, testDb1) 88 89 // check whether an existing db can be fetched for reading 90 _, err = boltdbIndexClient.GetDB(testDb1, DBOperationRead) 91 require.NoError(t, err) 92 93 // check whether read operation throws ErrUnexistentBoltDB error for db which does not exists 94 unexistentDb := "unexistent-db" 95 96 _, err = boltdbIndexClient.GetDB(unexistentDb, DBOperationRead) 97 require.Equal(t, ErrUnexistentBoltDB, err) 98 99 // check whether write operation sets up a new db for writing 100 db, err := boltdbIndexClient.GetDB(unexistentDb, DBOperationWrite) 101 require.NoError(t, err) 102 require.NotEqual(t, nil, db) 103 104 // recreate index client to check whether we can read already created test1 db without writing first 105 boltdbIndexClient.Stop() 106 boltdbIndexClient, err = NewBoltDBIndexClient(BoltDBConfig{ 107 Directory: dirname, 108 }) 109 require.NoError(t, err) 110 defer boltdbIndexClient.Stop() 111 112 _, err = boltdbIndexClient.GetDB(testDb1, DBOperationRead) 113 require.NoError(t, err) 114 } 115 116 func Test_CreateTable_BoltdbRW(t *testing.T) { 117 tableName := "test" 118 dirname := t.TempDir() 119 120 indexClient, err := NewBoltDBIndexClient(BoltDBConfig{ 121 Directory: dirname, 122 }) 123 require.NoError(t, err) 124 125 tableClient, err := NewTableClient(dirname) 126 require.NoError(t, err) 127 128 err = tableClient.CreateTable(context.Background(), config.TableDesc{ 129 Name: tableName, 130 }) 131 require.NoError(t, err) 132 133 batch := indexClient.NewWriteBatch() 134 batch.Add(tableName, fmt.Sprintf("hash%s", "test"), []byte(fmt.Sprintf("range%s", "value")), nil) 135 136 err = indexClient.BatchWrite(context.Background(), batch) 137 require.NoError(t, err) 138 139 // try to create the same file which is already existing 140 err = tableClient.CreateTable(context.Background(), config.TableDesc{ 141 Name: tableName, 142 }) 143 require.NoError(t, err) 144 145 // make sure file content is not modified 146 entry := index.Query{ 147 TableName: tableName, 148 HashValue: fmt.Sprintf("hash%s", "test"), 149 } 150 var have []index.Entry 151 err = indexClient.query(context.Background(), entry, func(_ index.Query, read index.ReadBatchResult) bool { 152 iter := read.Iterator() 153 for iter.Next() { 154 have = append(have, index.Entry{ 155 RangeValue: iter.RangeValue(), 156 }) 157 } 158 return true 159 }) 160 require.NoError(t, err) 161 require.Equal(t, []index.Entry{ 162 {RangeValue: []byte(fmt.Sprintf("range%s", "value"))}, 163 }, have) 164 } 165 166 func TestBoltDB_Writes(t *testing.T) { 167 dirname := t.TempDir() 168 169 for i, tc := range []struct { 170 name string 171 initialPuts []string 172 testPuts []string 173 testDeletes []string 174 err error 175 valuesAfterWrites []string 176 }{ 177 { 178 name: "just puts", 179 testPuts: []string{"1", "2"}, 180 valuesAfterWrites: []string{"1", "2"}, 181 }, 182 { 183 name: "just deletes", 184 initialPuts: []string{"1", "2", "3", "4"}, 185 testDeletes: []string{"1", "2"}, 186 valuesAfterWrites: []string{"3", "4"}, 187 }, 188 { 189 name: "both puts and deletes", 190 initialPuts: []string{"1", "2", "3", "4"}, 191 testPuts: []string{"5", "6"}, 192 testDeletes: []string{"1", "2"}, 193 valuesAfterWrites: []string{"3", "4", "5", "6"}, 194 }, 195 { 196 name: "deletes without initial writes", 197 testDeletes: []string{"1", "2"}, 198 err: fmt.Errorf("bucket %s not found in table 3", IndexBucketName), 199 }, 200 } { 201 t.Run(tc.name, func(t *testing.T) { 202 tableName := fmt.Sprint(i) 203 204 indexClient, err := NewBoltDBIndexClient(BoltDBConfig{ 205 Directory: dirname, 206 }) 207 require.NoError(t, err) 208 209 defer func() { 210 indexClient.Stop() 211 }() 212 213 // doing initial writes if there are any 214 if len(tc.initialPuts) != 0 { 215 batch := indexClient.NewWriteBatch() 216 for _, put := range tc.initialPuts { 217 batch.Add(tableName, "hash", []byte(put), []byte(put)) 218 } 219 220 require.NoError(t, indexClient.BatchWrite(context.Background(), batch)) 221 } 222 223 // doing writes with testPuts and testDeletes 224 batch := indexClient.NewWriteBatch() 225 for _, put := range tc.testPuts { 226 batch.Add(tableName, "hash", []byte(put), []byte(put)) 227 } 228 for _, put := range tc.testDeletes { 229 batch.Delete(tableName, "hash", []byte(put)) 230 } 231 232 require.Equal(t, tc.err, indexClient.BatchWrite(context.Background(), batch)) 233 234 // verifying test writes by querying 235 var have []index.Entry 236 err = indexClient.query(context.Background(), index.Query{ 237 TableName: tableName, 238 HashValue: "hash", 239 }, func(_ index.Query, read index.ReadBatchResult) bool { 240 iter := read.Iterator() 241 for iter.Next() { 242 have = append(have, index.Entry{ 243 RangeValue: iter.RangeValue(), 244 Value: iter.Value(), 245 }) 246 } 247 return true 248 }) 249 250 require.NoError(t, err) 251 require.Len(t, have, len(tc.valuesAfterWrites)) 252 253 for i, value := range tc.valuesAfterWrites { 254 require.Equal(t, index.Entry{ 255 RangeValue: []byte(value), 256 Value: []byte(value), 257 }, have[i]) 258 } 259 }) 260 } 261 } 262 263 func Benchmark_Query(b *testing.B) { 264 tableName := "test" 265 dirname := b.TempDir() 266 267 indexClient, err := NewBoltDBIndexClient(BoltDBConfig{ 268 Directory: dirname, 269 }) 270 require.NoError(b, err) 271 272 tableClient, err := NewTableClient(dirname) 273 require.NoError(b, err) 274 275 err = tableClient.CreateTable(context.Background(), config.TableDesc{ 276 Name: tableName, 277 }) 278 require.NoError(b, err) 279 280 batch := indexClient.NewWriteBatch() 281 batch.Add(tableName, fmt.Sprintf("hash%s", "test"), []byte(fmt.Sprintf("range%s", "value")), nil) 282 283 err = indexClient.BatchWrite(context.Background(), batch) 284 require.NoError(b, err) 285 286 // try to create the same file which is already existing 287 err = tableClient.CreateTable(context.Background(), config.TableDesc{ 288 Name: tableName, 289 }) 290 require.NoError(b, err) 291 292 // make sure file content is not modified 293 entry := index.Query{ 294 TableName: tableName, 295 HashValue: fmt.Sprintf("hash%s", "test"), 296 } 297 b.ResetTimer() 298 b.ReportAllocs() 299 for i := 0; i < b.N; i++ { 300 err = indexClient.query(context.Background(), entry, func(_ index.Query, read index.ReadBatchResult) bool { 301 iter := read.Iterator() 302 for iter.Next() { 303 } 304 return true 305 }) 306 require.NoError(b, err) 307 } 308 }