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  }