github.com/pingcap/tiflow@v0.0.0-20240520035814-5bf52d54e205/pkg/p2p/message_router_test.go (about)

     1  // Copyright 2021 PingCAP, Inc.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // See the License for the specific language governing permissions and
    12  // limitations under the License.
    13  
    14  package p2p
    15  
    16  import (
    17  	"context"
    18  	"fmt"
    19  	"net"
    20  	"strings"
    21  	"sync"
    22  	"testing"
    23  	"time"
    24  
    25  	"github.com/pingcap/failpoint"
    26  	"github.com/pingcap/tiflow/pkg/security"
    27  	"github.com/pingcap/tiflow/proto/p2p"
    28  	"github.com/stretchr/testify/require"
    29  	"github.com/tikv/pd/pkg/utils/tempurl"
    30  	"google.golang.org/grpc"
    31  )
    32  
    33  type messageRouterTestSuite struct {
    34  	servers       map[NodeID]*MessageServer
    35  	cancels       map[NodeID]context.CancelFunc
    36  	messageRouter *messageRouterImpl
    37  	wg            sync.WaitGroup
    38  }
    39  
    40  // read only
    41  var clientConfig4TestingMessageRouter = &MessageClientConfig{
    42  	SendChannelSize:         128,
    43  	BatchSendInterval:       time.Millisecond * 200,
    44  	MaxBatchCount:           128,
    45  	MaxBatchBytes:           8192,
    46  	RetryRateLimitPerSecond: 10.0, // using 10.0 instead of 1.0 to accelerate testing
    47  	DialTimeout:             time.Second * 3,
    48  	MaxRecvMsgSize:          4 * 1024 * 1024, // 4MB
    49  }
    50  
    51  func newMessageRouterTestSuite() *messageRouterTestSuite {
    52  	return &messageRouterTestSuite{
    53  		servers: map[NodeID]*MessageServer{},
    54  		cancels: map[NodeID]context.CancelFunc{},
    55  		messageRouter: NewMessageRouterWithLocalClient(
    56  			"test-client-1",
    57  			&security.Credential{},
    58  			clientConfig4TestingMessageRouter),
    59  	}
    60  }
    61  
    62  func (s *messageRouterTestSuite) getServer(id NodeID) *MessageServer {
    63  	return s.servers[id]
    64  }
    65  
    66  func (s *messageRouterTestSuite) addServer(ctx context.Context, t *testing.T, id NodeID) {
    67  	addr := strings.TrimPrefix(tempurl.Alloc(), "http://")
    68  	lis, err := net.Listen("tcp", addr)
    69  	require.NoError(t, err)
    70  
    71  	grpcServer := grpc.NewServer()
    72  	newServer := NewMessageServer(id, defaultServerConfig4Testing)
    73  	p2p.RegisterCDCPeerToPeerServer(grpcServer, newServer)
    74  
    75  	ctx, cancel := context.WithCancel(ctx)
    76  	s.cancels[id] = cancel
    77  	s.servers[id] = newServer
    78  
    79  	s.messageRouter.AddPeer(id, addr)
    80  
    81  	s.wg.Add(1)
    82  	go func() {
    83  		defer s.wg.Done()
    84  		_ = grpcServer.Serve(lis)
    85  	}()
    86  
    87  	s.wg.Add(1)
    88  	go func() {
    89  		defer s.wg.Done()
    90  		defer grpcServer.Stop()
    91  		defer s.messageRouter.RemovePeer(id)
    92  		err := newServer.Run(ctx, nil)
    93  		if err != nil {
    94  			require.Regexp(t, ".*context canceled.*", err.Error())
    95  		}
    96  	}()
    97  }
    98  
    99  func (s *messageRouterTestSuite) close() {
   100  	for _, cancel := range s.cancels {
   101  		cancel()
   102  	}
   103  	s.wg.Wait()
   104  
   105  	s.messageRouter.Close()
   106  }
   107  
   108  func TestMessageRouterBasic(t *testing.T) {
   109  	ctx, cancel := context.WithTimeout(context.TODO(), defaultTimeout)
   110  	defer cancel()
   111  
   112  	suite := newMessageRouterTestSuite()
   113  	suite.addServer(ctx, t, "server-1")
   114  	suite.addServer(ctx, t, "server-2")
   115  	suite.addServer(ctx, t, "server-3")
   116  
   117  	selfID := suite.messageRouter.selfID
   118  	localClient := suite.messageRouter.GetClient(selfID)
   119  	require.NotNil(t, localClient)
   120  	require.NotNil(t, suite.messageRouter.GetLocalChannel())
   121  
   122  	noClient := suite.messageRouter.GetClient("server-4")
   123  	require.Nilf(t, noClient, "no client should have been created")
   124  
   125  	var lastIndex [3]int64
   126  	mustAddHandler(ctx, t, suite.getServer("server-1"), "test-topic", &testTopicContent{}, func(senderID string, i interface{}) error {
   127  		require.Equal(t, "test-client-1", senderID)
   128  		require.IsType(t, &testTopicContent{}, i)
   129  		content := i.(*testTopicContent)
   130  		require.Equal(t, content.Index, lastIndex[0]+1)
   131  		lastIndex[0] = content.Index
   132  		return nil
   133  	})
   134  
   135  	mustAddHandler(ctx, t, suite.getServer("server-2"), "test-topic", &testTopicContent{}, func(senderID string, i interface{}) error {
   136  		require.Equal(t, "test-client-1", senderID)
   137  		require.IsType(t, &testTopicContent{}, i)
   138  		content := i.(*testTopicContent)
   139  		require.Equal(t, content.Index, lastIndex[1]+1)
   140  		lastIndex[1] = content.Index
   141  		return nil
   142  	})
   143  
   144  	mustAddHandler(ctx, t, suite.getServer("server-3"), "test-topic", &testTopicContent{}, func(senderID string, i interface{}) error {
   145  		require.Equal(t, "test-client-1", senderID)
   146  		require.IsType(t, &testTopicContent{}, i)
   147  		content := i.(*testTopicContent)
   148  		require.Equal(t, content.Index, lastIndex[2]+1)
   149  		lastIndex[2] = content.Index
   150  		return nil
   151  	})
   152  
   153  	var lastSeq [3]Seq
   154  	for i := 0; i < defaultMessageBatchSizeLarge; i++ {
   155  		serverIdx := i % 3
   156  		serverID := fmt.Sprintf("server-%d", serverIdx+1)
   157  		Seq, err := suite.messageRouter.GetClient(serverID).SendMessage(ctx, "test-topic", &testTopicContent{int64(i/3) + 1})
   158  		require.NoError(t, err)
   159  		lastSeq[serverIdx] = Seq
   160  	}
   161  
   162  	require.Eventually(t, func() bool {
   163  		seq, ok := suite.messageRouter.GetClient("server-1").CurrentAck("test-topic")
   164  		if !ok {
   165  			return false
   166  		}
   167  		return seq >= lastSeq[0]
   168  	}, time.Second*10, time.Millisecond*20)
   169  
   170  	require.Eventually(t, func() bool {
   171  		seq, ok := suite.messageRouter.GetClient("server-2").CurrentAck("test-topic")
   172  		if !ok {
   173  			return false
   174  		}
   175  		return seq >= lastSeq[1]
   176  	}, time.Second*10, time.Millisecond*20)
   177  
   178  	require.Eventually(t, func() bool {
   179  		seq, ok := suite.messageRouter.GetClient("server-3").CurrentAck("test-topic")
   180  		if !ok {
   181  			return false
   182  		}
   183  		return seq >= lastSeq[2]
   184  	}, time.Second*10, time.Millisecond*20)
   185  
   186  	suite.close()
   187  	suite.close() // double close: should not panic
   188  	suite.close() // triple close: should not panic
   189  }
   190  
   191  func TestMessageRouterRemovePeer(t *testing.T) {
   192  	ctx, cancel := context.WithTimeout(context.TODO(), defaultTimeout)
   193  	defer cancel()
   194  
   195  	suite := newMessageRouterTestSuite()
   196  	suite.addServer(ctx, t, "server-1")
   197  	suite.addServer(ctx, t, "server-2")
   198  
   199  	var lastIndex [3]int64
   200  	mustAddHandler(ctx, t, suite.getServer("server-1"), "test-topic", &testTopicContent{}, func(senderID string, i interface{}) error {
   201  		require.Equal(t, "test-client-1", senderID)
   202  		require.IsType(t, &testTopicContent{}, i)
   203  		content := i.(*testTopicContent)
   204  		require.Equal(t, content.Index, lastIndex[0]+1)
   205  		lastIndex[0] = content.Index
   206  		return nil
   207  	})
   208  
   209  	mustAddHandler(ctx, t, suite.getServer("server-2"), "test-topic", &testTopicContent{}, func(senderID string, i interface{}) error {
   210  		require.Equal(t, "test-client-1", senderID)
   211  		require.IsType(t, &testTopicContent{}, i)
   212  		content := i.(*testTopicContent)
   213  		require.Equal(t, content.Index, lastIndex[1]+1)
   214  		lastIndex[1] = content.Index
   215  		return nil
   216  	})
   217  
   218  	var wg sync.WaitGroup
   219  	wg.Add(1)
   220  	go func() {
   221  		defer wg.Done()
   222  		var lastSeq Seq
   223  		for i := 0; i < defaultMessageBatchSizeLarge; i++ {
   224  			var err error
   225  			lastSeq, err = suite.messageRouter.GetClient("server-1").
   226  				SendMessage(ctx, "test-topic", &testTopicContent{int64(i + 1)})
   227  			require.NoError(t, err)
   228  		}
   229  		require.Eventually(t, func() bool {
   230  			seq, ok := suite.messageRouter.GetClient("server-1").CurrentAck("test-topic")
   231  			if !ok {
   232  				return false
   233  			}
   234  			return seq >= lastSeq
   235  		}, time.Second*10, time.Millisecond*20)
   236  	}()
   237  
   238  	wg.Add(1)
   239  	go func() {
   240  		defer wg.Done()
   241  		client := suite.messageRouter.GetClient("server-2")
   242  		require.NotNil(t, client)
   243  		for i := 0; i < defaultMessageBatchSizeSmall; i++ {
   244  			var err error
   245  			_, err = client.SendMessage(ctx, "test-topic", &testTopicContent{int64(i + 1)})
   246  			require.NoError(t, err)
   247  		}
   248  		suite.messageRouter.RemovePeer("server-2")
   249  
   250  		var err error
   251  		require.Eventually(t, func() bool {
   252  			_, err = client.SendMessage(ctx, "test-topic", &testTopicContent{0})
   253  			return err != nil
   254  		}, time.Millisecond*500, time.Millisecond*50)
   255  		require.Regexp(t, ".*ErrPeerMessageClientClosed.*", err.Error())
   256  	}()
   257  
   258  	wg.Wait()
   259  	suite.close()
   260  }
   261  
   262  func TestMessageRouterClientFailure(t *testing.T) {
   263  	ctx, cancel := context.WithTimeout(context.TODO(), defaultTimeout)
   264  	defer cancel()
   265  
   266  	suite := newMessageRouterTestSuite()
   267  	suite.addServer(ctx, t, "server-1")
   268  
   269  	// FIXME remove the failpoint test, and use a mock client instead
   270  	// But we should make MessageClient an interface first.
   271  	err := failpoint.Enable("github.com/pingcap/tiflow/pkg/p2p/InjectClientPermanentFailure", "return(true)")
   272  	require.NoError(t, err)
   273  	defer func() {
   274  		_ = failpoint.Disable("github.com/pingcap/tiflow/pkg/p2p/InjectClientPermanentFailure")
   275  	}()
   276  
   277  	client := suite.messageRouter.GetClient("server-1")
   278  	require.NotNil(t, client)
   279  
   280  	select {
   281  	case <-ctx.Done():
   282  		require.Fail(t, "TestMessageRouterClientFailure timed out")
   283  	case err := <-suite.messageRouter.Err():
   284  		require.NotNil(t, err)
   285  		require.Regexp(t, ".*ErrPeerMessageClientPermanentFail.*", err.Error())
   286  	}
   287  
   288  	suite.close()
   289  }