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  }