github.com/true-sqn/fabric@v2.1.1+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_SIGNATURES] = protoutil.MarshalOrPanic(&common.Metadata{
   120  		Value: protoutil.MarshalOrPanic(&common.OrdererBlockMetadata{
   121  			LastConfig: &common.LastConfig{Index: 9},
   122  		}),
   123  	})
   124  
   125  	puller := &mocks.ChainPuller{}
   126  	puller.On("Close")
   127  	puller.On("HeightsByEndpoints").Return(map[string]uint64{"foo": 10}, nil)
   128  	puller.On("PullBlock", uint64(9)).Return(configBlock)
   129  
   130  	for _, testCase := range []struct {
   131  		description                 string
   132  		expectedPanic               string
   133  		expectedLog                 string
   134  		expectedCommittedBlockCount int
   135  		amIInChannelReturns         error
   136  		evictionSuspicionThreshold  time.Duration
   137  		blockPuller                 BlockPuller
   138  		blockPullerErr              error
   139  		height                      uint64
   140  		halt                        func()
   141  	}{
   142  		{
   143  			description:                "suspected time is lower than threshold",
   144  			evictionSuspicionThreshold: 11 * time.Minute,
   145  			halt:                       t.Fail,
   146  		},
   147  		{
   148  			description:                "puller creation fails",
   149  			evictionSuspicionThreshold: 10*time.Minute - time.Second,
   150  			blockPullerErr:             errors.New("oops"),
   151  			expectedPanic:              "Failed creating a block puller: oops",
   152  			halt:                       t.Fail,
   153  		},
   154  		{
   155  			description:                "our height is the highest",
   156  			expectedLog:                "Our height is higher or equal than the height of the orderer we pulled the last block from, aborting",
   157  			evictionSuspicionThreshold: 10*time.Minute - time.Second,
   158  			blockPuller:                puller,
   159  			height:                     10,
   160  			halt:                       t.Fail,
   161  		},
   162  		{
   163  			description:                "failed pulling the block",
   164  			expectedLog:                "Cannot confirm our own eviction from the channel: bad block",
   165  			evictionSuspicionThreshold: 10*time.Minute - time.Second,
   166  			amIInChannelReturns:        errors.New("bad block"),
   167  			blockPuller:                puller,
   168  			height:                     9,
   169  			halt:                       t.Fail,
   170  		},
   171  		{
   172  			description:                "we are still in the channel",
   173  			expectedLog:                "Cannot confirm our own eviction from the channel, our certificate was found in config block with sequence 9",
   174  			evictionSuspicionThreshold: 10*time.Minute - time.Second,
   175  			amIInChannelReturns:        nil,
   176  			blockPuller:                puller,
   177  			height:                     9,
   178  			halt:                       t.Fail,
   179  		},
   180  		{
   181  			description:                 "we are not in the channel",
   182  			expectedLog:                 "Detected our own eviction from the channel in block [9]",
   183  			evictionSuspicionThreshold:  10*time.Minute - time.Second,
   184  			amIInChannelReturns:         cluster.ErrNotInChannel,
   185  			blockPuller:                 puller,
   186  			height:                      8,
   187  			expectedCommittedBlockCount: 2,
   188  			halt: func() {
   189  				puller.On("PullBlock", uint64(8)).Return(&common.Block{
   190  					Header: &common.BlockHeader{Number: 8},
   191  					Metadata: &common.BlockMetadata{
   192  						Metadata: [][]byte{{}, {}, {}, {}},
   193  					},
   194  				})
   195  			},
   196  		},
   197  	} {
   198  		testCase := testCase
   199  		t.Run(testCase.description, func(t *testing.T) {
   200  			committedBlocks := make(chan *common.Block, 2)
   201  
   202  			commitBlock := func(block *common.Block) error {
   203  				committedBlocks <- block
   204  				return nil
   205  			}
   206  
   207  			es := &evictionSuspector{
   208  				halt: testCase.halt,
   209  				amIInChannel: func(_ *common.Block) error {
   210  					return testCase.amIInChannelReturns
   211  				},
   212  				evictionSuspicionThreshold: testCase.evictionSuspicionThreshold,
   213  				createPuller: func() (BlockPuller, error) {
   214  					return testCase.blockPuller, testCase.blockPullerErr
   215  				},
   216  				writeBlock: commitBlock,
   217  				height: func() uint64 {
   218  					return testCase.height
   219  				},
   220  				logger:         flogging.MustGetLogger("test"),
   221  				triggerCatchUp: func(sn *raftpb.Snapshot) { return },
   222  			}
   223  
   224  			foundExpectedLog := testCase.expectedLog == ""
   225  			es.logger = es.logger.WithOptions(zap.Hooks(func(entry zapcore.Entry) error {
   226  				if strings.Contains(entry.Message, testCase.expectedLog) {
   227  					foundExpectedLog = true
   228  				}
   229  				return nil
   230  			}))
   231  
   232  			runTestCase := func() {
   233  				es.confirmSuspicion(time.Minute * 10)
   234  			}
   235  
   236  			if testCase.expectedPanic != "" {
   237  				assert.PanicsWithValue(t, testCase.expectedPanic, runTestCase)
   238  			} else {
   239  				runTestCase()
   240  				// Run the test case again.
   241  				// Conditions that do not lead to a conclusion of a chain eviction
   242  				// should be idempotent.
   243  				// Conditions that do lead to conclusion of a chain eviction
   244  				// in the second time - should result in a no-op.
   245  				runTestCase()
   246  			}
   247  
   248  			assert.True(t, foundExpectedLog, "expected to find %s but didn't", testCase.expectedLog)
   249  			assert.Equal(t, testCase.expectedCommittedBlockCount, len(committedBlocks))
   250  		})
   251  	}
   252  }