github.com/renegr87/renegr87@v2.1.1+incompatible/core/ledger/kvledger/txmgmt/statedb/statecouchdb/redolog_test.go (about)

     1  /*
     2  Copyright IBM Corp. All Rights Reserved.
     3  
     4  SPDX-License-Identifier: Apache-2.0
     5  */
     6  
     7  package statecouchdb
     8  
     9  import (
    10  	"fmt"
    11  	"io/ioutil"
    12  	"os"
    13  	"testing"
    14  
    15  	"github.com/davecgh/go-spew/spew"
    16  	"github.com/hyperledger/fabric/core/ledger/kvledger/txmgmt/statedb"
    17  	"github.com/hyperledger/fabric/core/ledger/kvledger/txmgmt/version"
    18  	"github.com/stretchr/testify/assert"
    19  )
    20  
    21  func TestRedoLogger(t *testing.T) {
    22  	provider, cleanup := redologTestSetup(t)
    23  	defer cleanup()
    24  
    25  	loggers := []*redoLogger{}
    26  	records := []*redoRecord{}
    27  
    28  	verifyLogRecords := func() {
    29  		for i := 0; i < len(loggers); i++ {
    30  			retrievedRec, err := loggers[i].load()
    31  			assert.NoError(t, err)
    32  			assert.Equal(t, records[i], retrievedRec)
    33  		}
    34  	}
    35  
    36  	// write log records for multiple channels
    37  	for i := 0; i < 10; i++ {
    38  		logger := provider.newRedoLogger(fmt.Sprintf("channel-%d", i))
    39  		rec, err := logger.load()
    40  		assert.NoError(t, err)
    41  		assert.Nil(t, rec)
    42  		loggers = append(loggers, logger)
    43  		batch := statedb.NewUpdateBatch()
    44  		blkNum := uint64(i)
    45  		batch.Put("ns1", "key1", []byte("value1"), version.NewHeight(blkNum, 1))
    46  		batch.Put("ns2", string([]byte{0x00, 0xff}), []byte("value3"), version.NewHeight(blkNum, 3))
    47  		batch.PutValAndMetadata("ns2", string([]byte{0x00, 0xff}), []byte("value3"), []byte("metadata"), version.NewHeight(blkNum, 4))
    48  		batch.Delete("ns2", string([]byte{0xff, 0xff}), version.NewHeight(blkNum, 5))
    49  		rec = &redoRecord{
    50  			UpdateBatch: batch,
    51  			Version:     version.NewHeight(blkNum, 10),
    52  		}
    53  		records = append(records, rec)
    54  		assert.NoError(t, logger.persist(rec))
    55  	}
    56  
    57  	verifyLogRecords()
    58  	// overwrite logrecord for one channel
    59  	records[5].UpdateBatch = statedb.NewUpdateBatch()
    60  	records[5].Version = version.NewHeight(5, 5)
    61  	assert.NoError(t, loggers[5].persist(records[5]))
    62  	verifyLogRecords()
    63  }
    64  
    65  func TestCouchdbRedoLogger(t *testing.T) {
    66  	testEnv.init(t, &statedb.Cache{})
    67  	defer testEnv.cleanup()
    68  
    69  	// commitToRedologAndRestart - a helper function that commits directly to redologs and restart the statedb
    70  	commitToRedologAndRestart := func(newVal string, version *version.Height) {
    71  		batch := statedb.NewUpdateBatch()
    72  		batch.Put("ns1", "key1", []byte(newVal), version)
    73  		db, err := testEnv.DBProvider.GetDBHandle("testcouchdbredologger")
    74  		assert.NoError(t, err)
    75  		vdb := db.(*VersionedDB)
    76  		assert.NoError(t,
    77  			vdb.redoLogger.persist(
    78  				&redoRecord{
    79  					UpdateBatch: batch,
    80  					Version:     version,
    81  				},
    82  			),
    83  		)
    84  		testEnv.closeAndReopen()
    85  	}
    86  	// verifyExpectedVal - a helper function that verifies the statedb contents
    87  	verifyExpectedVal := func(expectedVal string, expectedSavepoint *version.Height) {
    88  		db, err := testEnv.DBProvider.GetDBHandle("testcouchdbredologger")
    89  		assert.NoError(t, err)
    90  		vdb := db.(*VersionedDB)
    91  		vv, err := vdb.GetState("ns1", "key1")
    92  		assert.NoError(t, err)
    93  		assert.Equal(t, expectedVal, string(vv.Value))
    94  		savepoint, err := vdb.GetLatestSavePoint()
    95  		assert.NoError(t, err)
    96  		assert.Equal(t, expectedSavepoint, savepoint)
    97  	}
    98  
    99  	// initialize statedb with initial set of writes
   100  	db, err := testEnv.DBProvider.GetDBHandle("testcouchdbredologger")
   101  	if err != nil {
   102  		t.Fatalf("Failed to get database handle: %s", err)
   103  	}
   104  	vdb := db.(*VersionedDB)
   105  	batch1 := statedb.NewUpdateBatch()
   106  	batch1.Put("ns1", "key1", []byte("value1"), version.NewHeight(1, 1))
   107  	vdb.ApplyUpdates(batch1, version.NewHeight(1, 1))
   108  
   109  	// make redolog one block ahead than statedb - upon restart the redolog should get applied
   110  	commitToRedologAndRestart("value2", version.NewHeight(2, 1))
   111  	verifyExpectedVal("value2", version.NewHeight(2, 1))
   112  
   113  	// make redolog two blocks ahead than statedb - upon restart the redolog should be ignored
   114  	commitToRedologAndRestart("value3", version.NewHeight(4, 1))
   115  	verifyExpectedVal("value2", version.NewHeight(2, 1))
   116  
   117  	// make redolog one block behind than statedb - upon restart the redolog should be ignored
   118  	commitToRedologAndRestart("value3", version.NewHeight(1, 5))
   119  	verifyExpectedVal("value2", version.NewHeight(2, 1))
   120  
   121  	// A nil height should cause skipping the writing of redo-record
   122  	db, _ = testEnv.DBProvider.GetDBHandle("testcouchdbredologger")
   123  	vdb = db.(*VersionedDB)
   124  	vdb.ApplyUpdates(batch1, nil)
   125  	record, err := vdb.redoLogger.load()
   126  	assert.NoError(t, err)
   127  	assert.Equal(t, version.NewHeight(1, 5), record.Version)
   128  	assert.Equal(t, []byte("value3"), record.UpdateBatch.Get("ns1", "key1").Value)
   129  
   130  	// A batch that does not contain PostOrderWrites should cause skipping the writing of redo-record
   131  	db, _ = testEnv.DBProvider.GetDBHandle("testcouchdbredologger")
   132  	vdb = db.(*VersionedDB)
   133  	batchWithNoGeneratedWrites := batch1
   134  	batchWithNoGeneratedWrites.ContainsPostOrderWrites = false
   135  	vdb.ApplyUpdates(batchWithNoGeneratedWrites, version.NewHeight(2, 5))
   136  	record, err = vdb.redoLogger.load()
   137  	assert.NoError(t, err)
   138  	assert.Equal(t, version.NewHeight(1, 5), record.Version)
   139  	assert.Equal(t, []byte("value3"), record.UpdateBatch.Get("ns1", "key1").Value)
   140  
   141  	// A batch that contains PostOrderWrites should cause writing of redo-record
   142  	db, _ = testEnv.DBProvider.GetDBHandle("testcouchdbredologger")
   143  	vdb = db.(*VersionedDB)
   144  	batchWithGeneratedWrites := batch1
   145  	batchWithGeneratedWrites.ContainsPostOrderWrites = true
   146  	vdb.ApplyUpdates(batchWithNoGeneratedWrites, version.NewHeight(3, 4))
   147  	record, err = vdb.redoLogger.load()
   148  	assert.NoError(t, err)
   149  	assert.Equal(t, version.NewHeight(3, 4), record.Version)
   150  	assert.Equal(t, []byte("value1"), record.UpdateBatch.Get("ns1", "key1").Value)
   151  }
   152  
   153  func redologTestSetup(t *testing.T) (p *redoLoggerProvider, cleanup func()) {
   154  	dbPath, err := ioutil.TempDir("", "redolog")
   155  	if err != nil {
   156  		t.Fatalf("Failed to create redo log directory: %s", err)
   157  	}
   158  	p, err = newRedoLoggerProvider(dbPath)
   159  	assert.NoError(t, err)
   160  	cleanup = func() {
   161  		p.close()
   162  		assert.NoError(t, os.RemoveAll(dbPath))
   163  	}
   164  	return
   165  }
   166  
   167  // testGenerareRedoRecord is the code that generates a serialized redo record into a
   168  // file based on the current version of the code, so that the file with serialized data
   169  // can get checked into source control. The following test function
   170  // 'TestReadExistingRedoRecord' verifies data compatibility in later builds/releases.
   171  // Specifically, it verifies that the changes in the struct statedb.NewUpdateBatch
   172  // are compatible such that the redo records persisted from the earlier commit/release
   173  // can still be deserialized on later commits/releases.
   174  // In order to generate this serialized record, change this function name to start with
   175  // uppercase "T" so that execution of go test will generate the test file.
   176  func testGenerareRedoRecord(t *testing.T) {
   177  	val, err := encodeRedologVal(constructSampleRedoRecord())
   178  	assert.NoError(t, err)
   179  	assert.NoError(t, ioutil.WriteFile("testdata/persisted_redo_record", val, 0644))
   180  }
   181  
   182  func TestReadExistingRedoRecord(t *testing.T) {
   183  	b, err := ioutil.ReadFile("testdata/persisted_redo_record")
   184  	assert.NoError(t, err)
   185  	rec, err := decodeRedologVal(b)
   186  	assert.NoError(t, err)
   187  	t.Logf("rec = %s", spew.Sdump(rec))
   188  	assert.Equal(t, constructSampleRedoRecord(), rec)
   189  }
   190  
   191  func constructSampleRedoRecord() *redoRecord {
   192  	batch := statedb.NewUpdateBatch()
   193  	batch.Put("ns1", "key1", []byte("value1"), version.NewHeight(1, 1))
   194  	batch.Put("ns2", string([]byte{0x00, 0xff}), []byte("value3"), version.NewHeight(3, 3))
   195  	batch.PutValAndMetadata("ns2", string([]byte{0x00, 0xff}), []byte("value3"), []byte("metadata"), version.NewHeight(4, 4))
   196  	batch.Delete("ns2", string([]byte{0xff, 0xff}), version.NewHeight(5, 5))
   197  	return &redoRecord{
   198  		UpdateBatch: batch,
   199  		Version:     version.NewHeight(10, 10),
   200  	}
   201  }