github.com/pingcap/tiflow@v0.0.0-20240520035814-5bf52d54e205/cdc/capture/capture_test.go (about)

     1  // Copyright 2022 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 capture
    15  
    16  import (
    17  	"context"
    18  	"sync"
    19  	"testing"
    20  	"time"
    21  
    22  	"github.com/golang/mock/gomock"
    23  	"github.com/pingcap/tiflow/cdc/model"
    24  	mock_owner "github.com/pingcap/tiflow/cdc/owner/mock"
    25  	mock_processor "github.com/pingcap/tiflow/cdc/processor/mock"
    26  	"github.com/pingcap/tiflow/cdc/vars"
    27  	"github.com/pingcap/tiflow/pkg/config"
    28  	"github.com/pingcap/tiflow/pkg/etcd"
    29  	mock_etcd "github.com/pingcap/tiflow/pkg/etcd/mock"
    30  	"github.com/prometheus/client_golang/prometheus"
    31  	"github.com/stretchr/testify/require"
    32  	"go.etcd.io/etcd/client/pkg/v3/logutil"
    33  	clientv3 "go.etcd.io/etcd/client/v3"
    34  	"go.uber.org/zap"
    35  	"go.uber.org/zap/zapcore"
    36  )
    37  
    38  func TestReset(t *testing.T) {
    39  	ctx, cancel := context.WithCancel(context.Background())
    40  
    41  	// init etcd mocker
    42  	clientURL, etcdServer, err := etcd.SetupEmbedEtcd(t.TempDir())
    43  	require.Nil(t, err)
    44  	logConfig := logutil.DefaultZapLoggerConfig
    45  	logConfig.Level = zap.NewAtomicLevelAt(zapcore.DebugLevel)
    46  	etcdCli, err := clientv3.New(clientv3.Config{
    47  		Endpoints:   []string{clientURL.String()},
    48  		Context:     ctx,
    49  		LogConfig:   &logConfig,
    50  		DialTimeout: 3 * time.Second,
    51  	})
    52  	require.NoError(t, err)
    53  
    54  	client, err := etcd.NewCDCEtcdClient(ctx, etcdCli, etcd.DefaultCDCClusterID)
    55  	require.Nil(t, err)
    56  	// Close the client before the test function exits to prevent possible
    57  	// ctx leaks.
    58  	// Ref: https://github.com/grpc/grpc-go/blob/master/stream.go#L229
    59  	defer client.Close()
    60  
    61  	cp := NewCapture4Test(nil)
    62  	cp.EtcdClient = client
    63  
    64  	// simulate network isolation scenarios
    65  	etcdServer.Close()
    66  	wg := sync.WaitGroup{}
    67  	wg.Add(1)
    68  	go func() {
    69  		_, err = cp.reset(ctx)
    70  		require.Regexp(t, ".*context canceled.*", err)
    71  		wg.Done()
    72  	}()
    73  	time.Sleep(100 * time.Millisecond)
    74  	info, err := cp.Info()
    75  	require.Nil(t, err)
    76  	require.NotNil(t, info)
    77  	cancel()
    78  	wg.Wait()
    79  }
    80  
    81  type mockEtcdClient struct {
    82  	etcd.CDCEtcdClient
    83  	clientv3.Lease
    84  	called chan struct{}
    85  }
    86  
    87  func (m *mockEtcdClient) GetEtcdClient() *etcd.Client {
    88  	cli := &clientv3.Client{Lease: m}
    89  	return etcd.Wrap(cli, map[string]prometheus.Counter{})
    90  }
    91  
    92  func (m *mockEtcdClient) Grant(_ context.Context, _ int64) (*clientv3.LeaseGrantResponse, error) {
    93  	select {
    94  	case m.called <- struct{}{}:
    95  	default:
    96  	}
    97  	return nil, context.DeadlineExceeded
    98  }
    99  
   100  func TestRetryInternalContextDeadlineExceeded(t *testing.T) {
   101  	ctx, cancel := context.WithCancel(context.Background())
   102  
   103  	called := make(chan struct{}, 2)
   104  	cp := NewCapture4Test(nil)
   105  	// In the current implementation, the first RPC is grant.
   106  	// the mock client always retry DeadlineExceeded for the RPC.
   107  	cp.EtcdClient = &mockEtcdClient{called: called}
   108  
   109  	errCh := make(chan error, 1)
   110  	go func() {
   111  		errCh <- cp.Run(ctx)
   112  	}()
   113  	time.Sleep(100 * time.Millisecond)
   114  	// Waiting for Grant to be called.
   115  	<-called
   116  	time.Sleep(100 * time.Millisecond)
   117  	// Make sure it retrys
   118  	<-called
   119  
   120  	// Do not retry context canceled.
   121  	cancel()
   122  	select {
   123  	case err := <-errCh:
   124  		require.NoError(t, err)
   125  	case <-time.After(5 * time.Second):
   126  		require.Fail(t, "timeout")
   127  	}
   128  }
   129  
   130  func TestInfo(t *testing.T) {
   131  	cp := NewCapture4Test(nil)
   132  	cp.info = nil
   133  	require.NotPanics(t, func() { cp.Info() })
   134  }
   135  
   136  func TestDrainCaptureBySignal(t *testing.T) {
   137  	t.Parallel()
   138  
   139  	ctrl := gomock.NewController(t)
   140  	mm := mock_processor.NewMockManager(ctrl)
   141  	me := mock_etcd.NewMockCDCEtcdClient(ctrl)
   142  	cp := &captureImpl{
   143  		info: &model.CaptureInfo{
   144  			ID:            "capture-for-test",
   145  			AdvertiseAddr: "127.0.0.1", Version: "test",
   146  		},
   147  		processorManager: mm,
   148  		config:           config.GetDefaultServerConfig(),
   149  		EtcdClient:       me,
   150  	}
   151  	require.Equal(t, model.LivenessCaptureAlive, cp.Liveness())
   152  
   153  	done := cp.Drain()
   154  	select {
   155  	case <-done:
   156  		require.Equal(t, model.LivenessCaptureStopping, cp.Liveness())
   157  	case <-time.After(time.Second):
   158  		require.Fail(t, "timeout")
   159  	}
   160  }
   161  
   162  func TestDrainWaitsOwnerResign(t *testing.T) {
   163  	t.Parallel()
   164  
   165  	ctrl := gomock.NewController(t)
   166  	mo := mock_owner.NewMockOwner(ctrl)
   167  	mm := mock_processor.NewMockManager(ctrl)
   168  	me := mock_etcd.NewMockCDCEtcdClient(ctrl)
   169  	cp := &captureImpl{
   170  		EtcdClient: me,
   171  		info: &model.CaptureInfo{
   172  			ID:            "capture-for-test",
   173  			AdvertiseAddr: "127.0.0.1", Version: "test",
   174  		},
   175  		processorManager: mm,
   176  		owner:            mo,
   177  		config:           config.GetDefaultServerConfig(),
   178  	}
   179  	require.Equal(t, model.LivenessCaptureAlive, cp.Liveness())
   180  
   181  	mo.EXPECT().AsyncStop().Do(func() {}).AnyTimes()
   182  
   183  	done := cp.Drain()
   184  	select {
   185  	case <-time.After(3 * time.Second):
   186  		require.Fail(t, "timeout")
   187  	case <-done:
   188  		require.Equal(t, model.LivenessCaptureStopping, cp.Liveness())
   189  	}
   190  }
   191  
   192  type mockElection struct {
   193  	campaignRequestCh chan struct{}
   194  	campaignGrantCh   chan struct{}
   195  
   196  	campaignFlag, resignFlag bool
   197  }
   198  
   199  func (e *mockElection) campaign(ctx context.Context, key string) error {
   200  	e.campaignRequestCh <- struct{}{}
   201  	<-e.campaignGrantCh
   202  	e.campaignFlag = true
   203  	return nil
   204  }
   205  
   206  func (e *mockElection) resign(ctx context.Context) error {
   207  	e.resignFlag = true
   208  	return nil
   209  }
   210  
   211  func TestCampaignLiveness(t *testing.T) {
   212  	t.Parallel()
   213  
   214  	me := &mockElection{
   215  		campaignRequestCh: make(chan struct{}, 1),
   216  		campaignGrantCh:   make(chan struct{}, 1),
   217  	}
   218  	cp := &captureImpl{
   219  		config:   config.GetDefaultServerConfig(),
   220  		info:     &model.CaptureInfo{ID: "test"},
   221  		election: me,
   222  	}
   223  	globalVars := vars.NewGlobalVars4Test()
   224  	ctx := context.Background()
   225  	cp.liveness.Store(model.LivenessCaptureStopping)
   226  	err := cp.campaignOwner(ctx, globalVars)
   227  	require.Nil(t, err)
   228  	require.False(t, me.campaignFlag)
   229  
   230  	// Force set alive.
   231  	cp.liveness = model.LivenessCaptureAlive
   232  	wg := sync.WaitGroup{}
   233  	wg.Add(1)
   234  	go func() {
   235  		defer wg.Done()
   236  		// Grant campaign
   237  		g := <-me.campaignRequestCh
   238  		// Set liveness to stopping
   239  		cp.liveness.Store(model.LivenessCaptureStopping)
   240  		me.campaignGrantCh <- g
   241  	}()
   242  	err = cp.campaignOwner(ctx, globalVars)
   243  	require.Nil(t, err)
   244  	require.True(t, me.campaignFlag)
   245  	require.True(t, me.resignFlag)
   246  
   247  	wg.Wait()
   248  }