github.com/ewagmig/fabric@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 }