github.com/yous1230/fabric@v2.0.0-beta.0.20191224111736-74345bee6ac2+incompatible/orderer/consensus/etcdraft/eviction_test.go (about) 1 /* 2 Copyright IBM Corp. All Rights Reserved. 3 4 SPDX-License-Identifier: Apache-2.0 5 */ 6 7 package etcdraft 8 9 import ( 10 "strings" 11 "sync/atomic" 12 "testing" 13 "time" 14 15 "github.com/hyperledger/fabric-protos-go/common" 16 "github.com/hyperledger/fabric/common/flogging" 17 "github.com/hyperledger/fabric/orderer/common/cluster" 18 "github.com/hyperledger/fabric/orderer/common/cluster/mocks" 19 "github.com/hyperledger/fabric/protoutil" 20 "github.com/onsi/gomega" 21 "github.com/pkg/errors" 22 "github.com/stretchr/testify/assert" 23 "go.etcd.io/etcd/raft/raftpb" 24 "go.uber.org/zap" 25 "go.uber.org/zap/zapcore" 26 ) 27 28 func TestPeriodicCheck(t *testing.T) { 29 t.Parallel() 30 31 g := gomega.NewGomegaWithT(t) 32 33 var cond uint32 34 var checkNum uint32 35 36 fiveChecks := func() bool { 37 return atomic.LoadUint32(&checkNum) > uint32(5) 38 } 39 40 condition := func() bool { 41 atomic.AddUint32(&checkNum, 1) 42 return atomic.LoadUint32(&cond) == uint32(1) 43 } 44 45 reports := make(chan time.Duration, 1000) 46 47 report := func(duration time.Duration) { 48 reports <- duration 49 } 50 51 check := &PeriodicCheck{ 52 Logger: flogging.MustGetLogger("test"), 53 Condition: condition, 54 CheckInterval: time.Millisecond, 55 Report: report, 56 } 57 58 go check.Run() 59 60 g.Eventually(fiveChecks, time.Minute, time.Millisecond).Should(gomega.BeTrue()) 61 // trigger condition to be true 62 atomic.StoreUint32(&cond, 1) 63 g.Eventually(reports, time.Minute, time.Millisecond).Should(gomega.Not(gomega.BeEmpty())) 64 // read first report 65 firstReport := <-reports 66 g.Eventually(reports, time.Minute, time.Millisecond).Should(gomega.Not(gomega.BeEmpty())) 67 // read second report 68 secondReport := <-reports 69 // time increases between reports 70 g.Expect(secondReport).To(gomega.BeNumerically(">", firstReport)) 71 // wait for the reports channel to be full 72 g.Eventually(func() int { return len(reports) }, time.Minute, time.Millisecond).Should(gomega.BeNumerically("==", 1000)) 73 74 // trigger condition to be false 75 atomic.StoreUint32(&cond, 0) 76 77 var lastReport time.Duration 78 // drain the reports channel 79 for len(reports) > 0 { 80 select { 81 case report := <-reports: 82 lastReport = report 83 default: 84 break 85 } 86 } 87 88 // ensure the checks have been made 89 checksDoneSoFar := atomic.LoadUint32(&checkNum) 90 g.Consistently(reports, time.Second*2, time.Millisecond).Should(gomega.BeEmpty()) 91 checksDoneAfter := atomic.LoadUint32(&checkNum) 92 g.Expect(checksDoneAfter).To(gomega.BeNumerically(">", checksDoneSoFar)) 93 // but nothing has been reported 94 g.Expect(reports).To(gomega.BeEmpty()) 95 96 // trigger the condition again 97 atomic.StoreUint32(&cond, 1) 98 g.Eventually(reports, time.Minute, time.Millisecond).Should(gomega.Not(gomega.BeEmpty())) 99 // The first report is smaller than the last report, 100 // so the countdown has been reset when the condition was reset 101 firstReport = <-reports 102 g.Expect(lastReport).To(gomega.BeNumerically(">", firstReport)) 103 // Stop the periodic check. 104 check.Stop() 105 checkCountAfterStop := atomic.LoadUint32(&checkNum) 106 // Wait 50 times the check interval. 107 time.Sleep(check.CheckInterval * 50) 108 // Ensure that we cease checking the condition, hence the PeriodicCheck is stopped. 109 g.Expect(atomic.LoadUint32(&checkNum)).To(gomega.BeNumerically("<", checkCountAfterStop+2)) 110 } 111 112 func TestEvictionSuspector(t *testing.T) { 113 configBlock := &common.Block{ 114 Header: &common.BlockHeader{Number: 9}, 115 Metadata: &common.BlockMetadata{ 116 Metadata: [][]byte{{}, {}, {}, {}}, 117 }, 118 } 119 configBlock.Metadata.Metadata[common.BlockMetadataIndex_LAST_CONFIG] = protoutil.MarshalOrPanic(&common.Metadata{ 120 Value: protoutil.MarshalOrPanic(&common.LastConfig{Index: 9}), 121 }) 122 123 puller := &mocks.ChainPuller{} 124 puller.On("Close") 125 puller.On("HeightsByEndpoints").Return(map[string]uint64{"foo": 10}, nil) 126 puller.On("PullBlock", uint64(9)).Return(configBlock) 127 128 for _, testCase := range []struct { 129 description string 130 expectedPanic string 131 expectedLog string 132 expectedCommittedBlockCount int 133 amIInChannelReturns error 134 evictionSuspicionThreshold time.Duration 135 blockPuller BlockPuller 136 blockPullerErr error 137 height uint64 138 halt func() 139 }{ 140 { 141 description: "suspected time is lower than threshold", 142 evictionSuspicionThreshold: 11 * time.Minute, 143 halt: t.Fail, 144 }, 145 { 146 description: "puller creation fails", 147 evictionSuspicionThreshold: 10*time.Minute - time.Second, 148 blockPullerErr: errors.New("oops"), 149 expectedPanic: "Failed creating a block puller: oops", 150 halt: t.Fail, 151 }, 152 { 153 description: "our height is the highest", 154 expectedLog: "Our height is higher or equal than the height of the orderer we pulled the last block from, aborting", 155 evictionSuspicionThreshold: 10*time.Minute - time.Second, 156 blockPuller: puller, 157 height: 10, 158 halt: t.Fail, 159 }, 160 { 161 description: "failed pulling the block", 162 expectedLog: "Cannot confirm our own eviction from the channel: bad block", 163 evictionSuspicionThreshold: 10*time.Minute - time.Second, 164 amIInChannelReturns: errors.New("bad block"), 165 blockPuller: puller, 166 height: 9, 167 halt: t.Fail, 168 }, 169 { 170 description: "we are still in the channel", 171 expectedLog: "Cannot confirm our own eviction from the channel, our certificate was found in config block with sequence 9", 172 evictionSuspicionThreshold: 10*time.Minute - time.Second, 173 amIInChannelReturns: nil, 174 blockPuller: puller, 175 height: 9, 176 halt: t.Fail, 177 }, 178 { 179 description: "we are not in the channel", 180 expectedLog: "Detected our own eviction from the channel in block [9]", 181 evictionSuspicionThreshold: 10*time.Minute - time.Second, 182 amIInChannelReturns: cluster.ErrNotInChannel, 183 blockPuller: puller, 184 height: 8, 185 expectedCommittedBlockCount: 2, 186 halt: func() { 187 puller.On("PullBlock", uint64(8)).Return(&common.Block{ 188 Header: &common.BlockHeader{Number: 8}, 189 Metadata: &common.BlockMetadata{ 190 Metadata: [][]byte{{}, {}, {}, {}}, 191 }, 192 }) 193 }, 194 }, 195 } { 196 testCase := testCase 197 t.Run(testCase.description, func(t *testing.T) { 198 committedBlocks := make(chan *common.Block, 2) 199 200 commitBlock := func(block *common.Block) error { 201 committedBlocks <- block 202 return nil 203 } 204 205 es := &evictionSuspector{ 206 halt: testCase.halt, 207 amIInChannel: func(_ *common.Block) error { 208 return testCase.amIInChannelReturns 209 }, 210 evictionSuspicionThreshold: testCase.evictionSuspicionThreshold, 211 createPuller: func() (BlockPuller, error) { 212 return testCase.blockPuller, testCase.blockPullerErr 213 }, 214 writeBlock: commitBlock, 215 height: func() uint64 { 216 return testCase.height 217 }, 218 logger: flogging.MustGetLogger("test"), 219 triggerCatchUp: func(sn *raftpb.Snapshot) { return }, 220 } 221 222 foundExpectedLog := testCase.expectedLog == "" 223 es.logger = es.logger.WithOptions(zap.Hooks(func(entry zapcore.Entry) error { 224 if strings.Contains(entry.Message, testCase.expectedLog) { 225 foundExpectedLog = true 226 } 227 return nil 228 })) 229 230 runTestCase := func() { 231 es.confirmSuspicion(time.Minute * 10) 232 } 233 234 if testCase.expectedPanic != "" { 235 assert.PanicsWithValue(t, testCase.expectedPanic, runTestCase) 236 } else { 237 runTestCase() 238 // Run the test case again. 239 // Conditions that do not lead to a conclusion of a chain eviction 240 // should be idempotent. 241 // Conditions that do lead to conclusion of a chain eviction 242 // in the second time - should result in a no-op. 243 runTestCase() 244 } 245 246 assert.True(t, foundExpectedLog, "expected to find %s but didn't", testCase.expectedLog) 247 assert.Equal(t, testCase.expectedCommittedBlockCount, len(committedBlocks)) 248 }) 249 } 250 }