github.com/pingcap/tiflow@v0.0.0-20240520035814-5bf52d54e205/cdc/processor/manager_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 processor
    15  
    16  import (
    17  	"bytes"
    18  	"context"
    19  	"fmt"
    20  	"math"
    21  	"testing"
    22  	"time"
    23  
    24  	"github.com/pingcap/tiflow/cdc/model"
    25  	"github.com/pingcap/tiflow/cdc/vars"
    26  	"github.com/pingcap/tiflow/pkg/config"
    27  	"github.com/pingcap/tiflow/pkg/etcd"
    28  	"github.com/pingcap/tiflow/pkg/orchestrator"
    29  	"github.com/pingcap/tiflow/pkg/upstream"
    30  	"github.com/stretchr/testify/require"
    31  )
    32  
    33  type managerTester struct {
    34  	manager *managerImpl
    35  	state   *orchestrator.GlobalReactorState
    36  	tester  *orchestrator.ReactorStateTester
    37  	//nolint:unused
    38  	liveness model.Liveness
    39  }
    40  
    41  // NewManager4Test creates a new processor manager for test
    42  func NewManager4Test(
    43  	t *testing.T,
    44  	liveness *model.Liveness,
    45  	globalVars *vars.GlobalVars,
    46  ) *managerImpl {
    47  	captureInfo := &model.CaptureInfo{ID: "capture-test", AdvertiseAddr: "127.0.0.1:0000"}
    48  	cfg := config.NewDefaultSchedulerConfig()
    49  	m := NewManager(captureInfo, upstream.NewManager4Test(nil), liveness, cfg, globalVars).(*managerImpl)
    50  	m.newProcessor = func(
    51  		info *model.ChangeFeedInfo,
    52  		status *model.ChangeFeedStatus,
    53  		captureInfo *model.CaptureInfo,
    54  		changefeedID model.ChangeFeedID,
    55  		up *upstream.Upstream,
    56  		liveness *model.Liveness,
    57  		changefeedEpoch uint64,
    58  		cfg *config.SchedulerConfig,
    59  		client etcd.OwnerCaptureInfoClient,
    60  		globalVars *vars.GlobalVars,
    61  	) *processor {
    62  		return newProcessor4Test(t, info, status, captureInfo, m.liveness, cfg, false, client, globalVars)
    63  	}
    64  	return m
    65  }
    66  
    67  //nolint:unused
    68  func (s *managerTester) resetSuit(globalVars *vars.GlobalVars,
    69  	t *testing.T,
    70  ) {
    71  	s.manager = NewManager4Test(t, &s.liveness, globalVars)
    72  	s.state = orchestrator.NewGlobalStateForTest(etcd.DefaultCDCClusterID)
    73  	captureInfoBytes, err := globalVars.CaptureInfo.Marshal()
    74  	require.Nil(t, err)
    75  	s.tester = orchestrator.NewReactorStateTester(t, s.state, map[string]string{
    76  		fmt.Sprintf("%s/capture/%s",
    77  			etcd.DefaultClusterAndMetaPrefix,
    78  			globalVars.CaptureInfo.ID): string(captureInfoBytes),
    79  	})
    80  }
    81  
    82  func TestChangefeed(t *testing.T) {
    83  	globalVars := vars.NewGlobalVars4Test()
    84  	ctx := context.Background()
    85  	s := &managerTester{}
    86  	s.resetSuit(globalVars, t)
    87  	var err error
    88  
    89  	// no changefeed
    90  	_, err = s.manager.Tick(ctx, s.state)
    91  	require.Nil(t, err)
    92  
    93  	changefeedID := model.DefaultChangeFeedID("test-changefeed")
    94  	// an inactive changefeed
    95  	s.state.Changefeeds[changefeedID] = orchestrator.NewChangefeedReactorState(
    96  		etcd.DefaultCDCClusterID, changefeedID)
    97  	_, err = s.manager.Tick(ctx, s.state)
    98  	s.tester.MustApplyPatches()
    99  	require.Nil(t, err)
   100  	require.Len(t, s.manager.processors, 0)
   101  
   102  	// an active changefeed
   103  	s.state.Changefeeds[changefeedID].PatchInfo(
   104  		func(info *model.ChangeFeedInfo) (*model.ChangeFeedInfo, bool, error) {
   105  			return &model.ChangeFeedInfo{
   106  				SinkURI:    "blackhole://",
   107  				CreateTime: time.Now(),
   108  				StartTs:    0,
   109  				TargetTs:   math.MaxUint64,
   110  				Config:     config.GetDefaultReplicaConfig(),
   111  			}, true, nil
   112  		})
   113  	s.state.Changefeeds[changefeedID].PatchStatus(
   114  		func(status *model.ChangeFeedStatus) (*model.ChangeFeedStatus, bool, error) {
   115  			return &model.ChangeFeedStatus{}, true, nil
   116  		})
   117  	s.tester.MustApplyPatches()
   118  	_, err = s.manager.Tick(ctx, s.state)
   119  	s.tester.MustApplyPatches()
   120  	require.Nil(t, err)
   121  	require.Len(t, s.manager.processors, 1)
   122  
   123  	// processor return errors
   124  	s.state.Changefeeds[changefeedID].PatchStatus(
   125  		func(status *model.ChangeFeedStatus) (*model.ChangeFeedStatus, bool, error) {
   126  			status.AdminJobType = model.AdminStop
   127  			return status, true, nil
   128  		})
   129  	s.tester.MustApplyPatches()
   130  	_, err = s.manager.Tick(ctx, s.state)
   131  	s.tester.MustApplyPatches()
   132  	require.Nil(t, err)
   133  	require.Len(t, s.manager.processors, 0)
   134  }
   135  
   136  func TestDebugInfo(t *testing.T) {
   137  	globalVars := vars.NewGlobalVars4Test()
   138  	ctx := context.Background()
   139  	s := &managerTester{}
   140  	s.resetSuit(globalVars, t)
   141  	var err error
   142  
   143  	// no changefeed
   144  	_, err = s.manager.Tick(ctx, s.state)
   145  	require.Nil(t, err)
   146  
   147  	changefeedID := model.DefaultChangeFeedID("test-changefeed")
   148  	// an active changefeed
   149  	s.state.Changefeeds[changefeedID] = orchestrator.NewChangefeedReactorState(
   150  		etcd.DefaultCDCClusterID, changefeedID)
   151  	s.state.Changefeeds[changefeedID].PatchInfo(
   152  		func(info *model.ChangeFeedInfo) (*model.ChangeFeedInfo, bool, error) {
   153  			return &model.ChangeFeedInfo{
   154  				SinkURI:    "blackhole://",
   155  				CreateTime: time.Now(),
   156  				StartTs:    1,
   157  				TargetTs:   math.MaxUint64,
   158  				Config:     config.GetDefaultReplicaConfig(),
   159  			}, true, nil
   160  		})
   161  	s.state.Changefeeds[changefeedID].PatchStatus(
   162  		func(status *model.ChangeFeedStatus) (*model.ChangeFeedStatus, bool, error) {
   163  			return &model.ChangeFeedStatus{}, true, nil
   164  		})
   165  	s.tester.MustApplyPatches()
   166  	_, err = s.manager.Tick(ctx, s.state)
   167  	require.Nil(t, err)
   168  	s.tester.MustApplyPatches()
   169  	require.Len(t, s.manager.processors, 1)
   170  
   171  	// Do a no operation tick to lazy init the processor.
   172  	_, err = s.manager.Tick(ctx, s.state)
   173  	require.Nil(t, err)
   174  	s.tester.MustApplyPatches()
   175  
   176  	stdCtx, cancel := context.WithCancel(context.Background())
   177  	done := make(chan struct{})
   178  	go func() {
   179  		defer close(done)
   180  		for {
   181  			select {
   182  			case <-stdCtx.Done():
   183  				return
   184  			default:
   185  			}
   186  			_, err = s.manager.Tick(ctx, s.state)
   187  			require.Nil(t, err)
   188  			s.tester.MustApplyPatches()
   189  		}
   190  	}()
   191  	doneM := make(chan error, 1)
   192  	buf := bytes.NewBufferString("")
   193  	s.manager.WriteDebugInfo(ctx, buf, doneM)
   194  	<-doneM
   195  	require.Greater(t, len(buf.String()), 0)
   196  
   197  	// Stop tick so that we can close manager safely.
   198  	cancel()
   199  	<-done
   200  	s.manager.Close()
   201  }
   202  
   203  func TestClose(t *testing.T) {
   204  	globalVars := vars.NewGlobalVars4Test()
   205  	ctx := context.Background()
   206  	s := &managerTester{}
   207  	s.resetSuit(globalVars, t)
   208  	var err error
   209  
   210  	// no changefeed
   211  	_, err = s.manager.Tick(ctx, s.state)
   212  	require.Nil(t, err)
   213  
   214  	changefeedID := model.DefaultChangeFeedID("test-changefeed")
   215  	// an active changefeed
   216  	s.state.Changefeeds[changefeedID] = orchestrator.NewChangefeedReactorState(
   217  		etcd.DefaultCDCClusterID, changefeedID)
   218  	s.state.Changefeeds[changefeedID].PatchInfo(
   219  		func(info *model.ChangeFeedInfo) (*model.ChangeFeedInfo, bool, error) {
   220  			return &model.ChangeFeedInfo{
   221  				SinkURI:    "blackhole://",
   222  				CreateTime: time.Now(),
   223  				StartTs:    0,
   224  				TargetTs:   math.MaxUint64,
   225  				Config:     config.GetDefaultReplicaConfig(),
   226  			}, true, nil
   227  		})
   228  	s.state.Changefeeds[changefeedID].PatchStatus(
   229  		func(status *model.ChangeFeedStatus) (*model.ChangeFeedStatus, bool, error) {
   230  			return &model.ChangeFeedStatus{}, true, nil
   231  		})
   232  	s.tester.MustApplyPatches()
   233  	_, err = s.manager.Tick(ctx, s.state)
   234  	require.Nil(t, err)
   235  	s.tester.MustApplyPatches()
   236  	require.Len(t, s.manager.processors, 1)
   237  
   238  	s.manager.Close()
   239  	require.Len(t, s.manager.processors, 0)
   240  }
   241  
   242  func TestSendCommandError(t *testing.T) {
   243  	globalVars := vars.NewGlobalVars4Test()
   244  	liveness := model.LivenessCaptureAlive
   245  	cfg := config.NewDefaultSchedulerConfig()
   246  	m := NewManager(&model.CaptureInfo{ID: "capture-test"}, nil, &liveness, cfg, globalVars).(*managerImpl)
   247  	ctx, cancel := context.WithCancel(context.TODO())
   248  	cancel()
   249  	// Use unbuffered channel to stable test.
   250  	m.commandQueue = make(chan *command)
   251  	done := make(chan error, 1)
   252  	err := m.sendCommand(ctx, commandTpWriteDebugInfo, nil, done)
   253  	require.Error(t, err)
   254  	select {
   255  	case <-done:
   256  	case <-time.After(time.Second):
   257  		require.FailNow(t, "done must be closed")
   258  	}
   259  }
   260  
   261  func TestManagerLiveness(t *testing.T) {
   262  	globalVars := vars.NewGlobalVars4Test()
   263  	ctx := context.Background()
   264  	s := &managerTester{}
   265  	s.resetSuit(globalVars, t)
   266  	var err error
   267  
   268  	changefeedID := model.DefaultChangeFeedID("test-changefeed")
   269  
   270  	// no changefeed
   271  	_, err = s.manager.Tick(ctx, s.state)
   272  	require.Nil(t, err)
   273  	// an inactive changefeed
   274  	s.state.Changefeeds[changefeedID] = orchestrator.NewChangefeedReactorState(
   275  		etcd.DefaultCDCClusterID, changefeedID)
   276  	_, err = s.manager.Tick(ctx, s.state)
   277  	s.tester.MustApplyPatches()
   278  	require.Nil(t, err)
   279  	require.Len(t, s.manager.processors, 0)
   280  	// an active changefeed
   281  	s.state.Changefeeds[changefeedID].PatchInfo(
   282  		func(info *model.ChangeFeedInfo) (*model.ChangeFeedInfo, bool, error) {
   283  			return &model.ChangeFeedInfo{
   284  				SinkURI:    "blackhole://",
   285  				CreateTime: time.Now(),
   286  				StartTs:    0,
   287  				TargetTs:   math.MaxUint64,
   288  				Config:     config.GetDefaultReplicaConfig(),
   289  			}, true, nil
   290  		})
   291  	s.state.Changefeeds[changefeedID].PatchStatus(
   292  		func(status *model.ChangeFeedStatus) (*model.ChangeFeedStatus, bool, error) {
   293  			return &model.ChangeFeedStatus{}, true, nil
   294  		})
   295  	s.tester.MustApplyPatches()
   296  	_, err = s.manager.Tick(ctx, s.state)
   297  	s.tester.MustApplyPatches()
   298  	require.Nil(t, err)
   299  	require.Len(t, s.manager.processors, 1)
   300  
   301  	p := s.manager.processors[changefeedID]
   302  	require.Equal(t, model.LivenessCaptureAlive, p.liveness.Load())
   303  	s.liveness.Store(model.LivenessCaptureStopping)
   304  	require.Equal(t, model.LivenessCaptureStopping, p.liveness.Load())
   305  }