github.com/anjalikarhana/fabric@v2.1.1+incompatible/orderer/common/cluster/rpc_test.go (about)

     1  /*
     2  Copyright IBM Corp. 2017 All Rights Reserved.
     3  
     4  SPDX-License-Identifier: Apache-2.0
     5  */
     6  
     7  package cluster_test
     8  
     9  import (
    10  	"context"
    11  	"io"
    12  	"sync"
    13  	"sync/atomic"
    14  	"testing"
    15  	"time"
    16  
    17  	"github.com/hyperledger/fabric-protos-go/common"
    18  	"github.com/hyperledger/fabric-protos-go/orderer"
    19  	"github.com/hyperledger/fabric/common/flogging"
    20  	"github.com/hyperledger/fabric/common/metrics/disabled"
    21  	"github.com/hyperledger/fabric/orderer/common/cluster"
    22  	"github.com/hyperledger/fabric/orderer/common/cluster/mocks"
    23  	"github.com/pkg/errors"
    24  	"github.com/stretchr/testify/assert"
    25  	"github.com/stretchr/testify/mock"
    26  	"google.golang.org/grpc"
    27  )
    28  
    29  func TestRPCChangeDestination(t *testing.T) {
    30  	// We send a Submit() to 2 different nodes - 1 and 2.
    31  	// The first invocation of Submit() establishes a stream with node 1
    32  	// and the second establishes a stream with node 2.
    33  	// We define a mock behavior for only a single invocation of Send() on each
    34  	// of the streams (to node 1 and to node 2), therefore we test that invocation
    35  	// of rpc.SendSubmit to node 2 doesn't send the message to node 1.
    36  	comm := &mocks.Communicator{}
    37  
    38  	client1 := &mocks.ClusterClient{}
    39  	client2 := &mocks.ClusterClient{}
    40  
    41  	metrics := cluster.NewMetrics(&disabled.Provider{})
    42  
    43  	comm.On("Remote", "mychannel", uint64(1)).Return(&cluster.RemoteContext{
    44  		SendBuffSize: 10,
    45  		Metrics:      metrics,
    46  		Logger:       flogging.MustGetLogger("test"),
    47  		Client:       client1,
    48  		ProbeConn:    func(_ *grpc.ClientConn) error { return nil },
    49  	}, nil)
    50  	comm.On("Remote", "mychannel", uint64(2)).Return(&cluster.RemoteContext{
    51  		SendBuffSize: 10,
    52  		Metrics:      metrics,
    53  		Logger:       flogging.MustGetLogger("test"),
    54  		Client:       client2,
    55  		ProbeConn:    func(_ *grpc.ClientConn) error { return nil },
    56  	}, nil)
    57  
    58  	streamToNode1 := &mocks.StepClient{}
    59  	streamToNode2 := &mocks.StepClient{}
    60  	streamToNode1.On("Context", mock.Anything).Return(context.Background())
    61  	streamToNode2.On("Context", mock.Anything).Return(context.Background())
    62  
    63  	client1.On("Step", mock.Anything).Return(streamToNode1, nil).Once()
    64  	client2.On("Step", mock.Anything).Return(streamToNode2, nil).Once()
    65  
    66  	rpc := &cluster.RPC{
    67  		Logger:        flogging.MustGetLogger("test"),
    68  		Timeout:       time.Hour,
    69  		StreamsByType: cluster.NewStreamsByType(),
    70  		Channel:       "mychannel",
    71  		Comm:          comm,
    72  	}
    73  
    74  	var sent sync.WaitGroup
    75  	sent.Add(2)
    76  
    77  	signalSent := func(_ mock.Arguments) {
    78  		sent.Done()
    79  	}
    80  	streamToNode1.On("Send", mock.Anything).Return(nil).Run(signalSent).Once()
    81  	streamToNode2.On("Send", mock.Anything).Return(nil).Run(signalSent).Once()
    82  	streamToNode1.On("Recv").Return(nil, io.EOF)
    83  	streamToNode2.On("Recv").Return(nil, io.EOF)
    84  
    85  	rpc.SendSubmit(1, &orderer.SubmitRequest{Channel: "mychannel"})
    86  	rpc.SendSubmit(2, &orderer.SubmitRequest{Channel: "mychannel"})
    87  
    88  	sent.Wait()
    89  	streamToNode1.AssertNumberOfCalls(t, "Send", 1)
    90  	streamToNode2.AssertNumberOfCalls(t, "Send", 1)
    91  }
    92  
    93  func TestSend(t *testing.T) {
    94  	submitRequest := &orderer.SubmitRequest{Channel: "mychannel"}
    95  	submitResponse := &orderer.StepResponse{
    96  		Payload: &orderer.StepResponse_SubmitRes{
    97  			SubmitRes: &orderer.SubmitResponse{Status: common.Status_SUCCESS},
    98  		},
    99  	}
   100  
   101  	consensusRequest := &orderer.ConsensusRequest{
   102  		Channel: "mychannel",
   103  	}
   104  
   105  	submitReq := wrapSubmitReq(submitRequest)
   106  
   107  	consensusReq := &orderer.StepRequest{
   108  		Payload: &orderer.StepRequest_ConsensusRequest{
   109  			ConsensusRequest: consensusRequest,
   110  		},
   111  	}
   112  
   113  	submit := func(rpc *cluster.RPC) error {
   114  		err := rpc.SendSubmit(1, submitRequest)
   115  		return err
   116  	}
   117  
   118  	step := func(rpc *cluster.RPC) error {
   119  		return rpc.SendConsensus(1, consensusRequest)
   120  	}
   121  
   122  	type testCase struct {
   123  		name           string
   124  		method         func(rpc *cluster.RPC) error
   125  		sendReturns    error
   126  		sendCalledWith *orderer.StepRequest
   127  		receiveReturns []interface{}
   128  		stepReturns    []interface{}
   129  		remoteError    error
   130  		expectedErr    string
   131  	}
   132  
   133  	l := &sync.Mutex{}
   134  	var tst testCase
   135  
   136  	sent := make(chan struct{})
   137  
   138  	var sendCalls uint32
   139  
   140  	stream := &mocks.StepClient{}
   141  	stream.On("Context", mock.Anything).Return(context.Background())
   142  	stream.On("Send", mock.Anything).Return(func(*orderer.StepRequest) error {
   143  		l.Lock()
   144  		defer l.Unlock()
   145  		atomic.AddUint32(&sendCalls, 1)
   146  		sent <- struct{}{}
   147  		return tst.sendReturns
   148  	})
   149  
   150  	for _, tst := range []testCase{
   151  		{
   152  			name:           "Send and Receive submit succeed",
   153  			method:         submit,
   154  			sendReturns:    nil,
   155  			stepReturns:    []interface{}{stream, nil},
   156  			receiveReturns: []interface{}{submitResponse, nil},
   157  			sendCalledWith: submitReq,
   158  		},
   159  		{
   160  			name:           "Send step succeed",
   161  			method:         step,
   162  			sendReturns:    nil,
   163  			stepReturns:    []interface{}{stream, nil},
   164  			sendCalledWith: consensusReq,
   165  		},
   166  		{
   167  			name:           "Send submit fails",
   168  			method:         submit,
   169  			sendReturns:    errors.New("oops"),
   170  			stepReturns:    []interface{}{stream, nil},
   171  			sendCalledWith: submitReq,
   172  			expectedErr:    "stream is aborted",
   173  		},
   174  		{
   175  			name:           "Send step fails",
   176  			method:         step,
   177  			sendReturns:    errors.New("oops"),
   178  			stepReturns:    []interface{}{stream, nil},
   179  			sendCalledWith: consensusReq,
   180  			expectedErr:    "stream is aborted",
   181  		},
   182  		{
   183  			name:        "Remote() fails",
   184  			method:      submit,
   185  			remoteError: errors.New("timed out"),
   186  			stepReturns: []interface{}{stream, nil},
   187  			expectedErr: "timed out",
   188  		},
   189  		{
   190  			name:        "Submit fails with Send",
   191  			method:      submit,
   192  			stepReturns: []interface{}{nil, errors.New("deadline exceeded")},
   193  			expectedErr: "deadline exceeded",
   194  		},
   195  	} {
   196  		l.Lock()
   197  		testCase := tst
   198  		l.Unlock()
   199  
   200  		t.Run(testCase.name, func(t *testing.T) {
   201  			atomic.StoreUint32(&sendCalls, 0)
   202  			isSend := testCase.receiveReturns == nil
   203  			comm := &mocks.Communicator{}
   204  			client := &mocks.ClusterClient{}
   205  			client.On("Step", mock.Anything).Return(testCase.stepReturns...)
   206  			rm := &cluster.RemoteContext{
   207  				Metrics:      cluster.NewMetrics(&disabled.Provider{}),
   208  				SendBuffSize: 1,
   209  				Logger:       flogging.MustGetLogger("test"),
   210  				ProbeConn:    func(_ *grpc.ClientConn) error { return nil },
   211  				Client:       client,
   212  			}
   213  			defer rm.Abort()
   214  			comm.On("Remote", "mychannel", uint64(1)).Return(rm, testCase.remoteError)
   215  
   216  			rpc := &cluster.RPC{
   217  				Logger:        flogging.MustGetLogger("test"),
   218  				Timeout:       time.Hour,
   219  				StreamsByType: cluster.NewStreamsByType(),
   220  				Channel:       "mychannel",
   221  				Comm:          comm,
   222  			}
   223  
   224  			var err error
   225  
   226  			err = testCase.method(rpc)
   227  			if testCase.remoteError == nil && testCase.stepReturns[1] == nil {
   228  				<-sent
   229  			}
   230  
   231  			if testCase.stepReturns[1] == nil && testCase.remoteError == nil {
   232  				assert.NoError(t, err)
   233  			} else {
   234  				assert.EqualError(t, err, testCase.expectedErr)
   235  			}
   236  
   237  			if testCase.remoteError == nil && testCase.expectedErr == "" && isSend {
   238  				stream.AssertCalled(t, "Send", testCase.sendCalledWith)
   239  				// Ensure that if we succeeded - only 1 stream was created despite 2 calls
   240  				// to Send() were made
   241  				err := testCase.method(rpc)
   242  				<-sent
   243  
   244  				assert.NoError(t, err)
   245  				assert.Equal(t, 2, int(atomic.LoadUint32(&sendCalls)))
   246  				client.AssertNumberOfCalls(t, "Step", 1)
   247  			}
   248  		})
   249  	}
   250  }
   251  
   252  func TestRPCGarbageCollection(t *testing.T) {
   253  	// Scenario: Send a message to a remote node, and establish a stream
   254  	// while doing it.
   255  	// Afterwards - make that stream be aborted, and send a message to a different
   256  	// remote node.
   257  	// The first stream should be cleaned from the mapping.
   258  
   259  	comm := &mocks.Communicator{}
   260  	client := &mocks.ClusterClient{}
   261  	stream := &mocks.StepClient{}
   262  
   263  	remote := &cluster.RemoteContext{
   264  		SendBuffSize: 10,
   265  		Metrics:      cluster.NewMetrics(&disabled.Provider{}),
   266  		Logger:       flogging.MustGetLogger("test"),
   267  		Client:       client,
   268  		ProbeConn:    func(_ *grpc.ClientConn) error { return nil },
   269  	}
   270  
   271  	var sent sync.WaitGroup
   272  
   273  	defineMocks := func(destination uint64) {
   274  		sent.Add(1)
   275  		comm.On("Remote", "mychannel", destination).Return(remote, nil)
   276  		stream.On("Context", mock.Anything).Return(context.Background())
   277  		client.On("Step", mock.Anything).Return(stream, nil).Once()
   278  		stream.On("Send", mock.Anything).Return(nil).Once().Run(func(_ mock.Arguments) {
   279  			sent.Done()
   280  		})
   281  		stream.On("Recv").Return(nil, nil)
   282  	}
   283  
   284  	mapping := cluster.NewStreamsByType()
   285  
   286  	rpc := &cluster.RPC{
   287  		Logger:        flogging.MustGetLogger("test"),
   288  		Timeout:       time.Hour,
   289  		StreamsByType: mapping,
   290  		Channel:       "mychannel",
   291  		Comm:          comm,
   292  	}
   293  
   294  	defineMocks(1)
   295  
   296  	rpc.SendSubmit(1, &orderer.SubmitRequest{Channel: "mychannel"})
   297  	// Wait for the message to arrive
   298  	sent.Wait()
   299  	// Ensure the stream is initialized in the mapping
   300  	assert.Len(t, mapping[cluster.SubmitOperation], 1)
   301  	assert.Equal(t, uint64(1), mapping[cluster.SubmitOperation][1].ID)
   302  	// And the underlying gRPC stream indeed had Send invoked on it.
   303  	stream.AssertNumberOfCalls(t, "Send", 1)
   304  
   305  	// Abort all streams we currently have that are associated to the remote.
   306  	remote.Abort()
   307  
   308  	// The stream still exists, as it is not cleaned yet.
   309  	assert.Len(t, mapping[cluster.SubmitOperation], 1)
   310  	assert.Equal(t, uint64(1), mapping[cluster.SubmitOperation][1].ID)
   311  
   312  	// Prepare for the next transmission.
   313  	defineMocks(2)
   314  
   315  	// Send a message to a different node.
   316  	rpc.SendSubmit(2, &orderer.SubmitRequest{Channel: "mychannel"})
   317  	// The mapping should be now cleaned from the previous stream.
   318  	assert.Len(t, mapping[cluster.SubmitOperation], 1)
   319  	assert.Equal(t, uint64(2), mapping[cluster.SubmitOperation][2].ID)
   320  }