github.com/pingcap/tiflow@v0.0.0-20240520035814-5bf52d54e205/cdc/scheduler/internal/v3/member/capture_manager_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 member
    15  
    16  import (
    17  	"testing"
    18  
    19  	"github.com/pingcap/tiflow/cdc/model"
    20  	"github.com/pingcap/tiflow/cdc/processor/tablepb"
    21  	"github.com/pingcap/tiflow/cdc/scheduler/internal/v3/replication"
    22  	"github.com/pingcap/tiflow/cdc/scheduler/schedulepb"
    23  	"github.com/pingcap/tiflow/pkg/config"
    24  	"github.com/pingcap/tiflow/pkg/spanz"
    25  	"github.com/stretchr/testify/require"
    26  )
    27  
    28  const (
    29  	captureIDNotDraining = ""
    30  )
    31  
    32  func TestCaptureStatusHandleHeartbeatResponse(t *testing.T) {
    33  	t.Parallel()
    34  
    35  	rev := schedulepb.OwnerRevision{Revision: 1}
    36  	epoch := schedulepb.ProcessorEpoch{Epoch: "test"}
    37  	c := newCaptureStatus(rev, "", "", true, model.ChangeFeedID{})
    38  	require.Equal(t, CaptureStateUninitialized, c.State)
    39  	require.True(t, c.IsOwner)
    40  
    41  	// Uninitialized -> Initialized
    42  	c.handleHeartbeatResponse(&schedulepb.HeartbeatResponse{}, epoch)
    43  	require.Equal(t, CaptureStateInitialized, c.State)
    44  	require.Equal(t, epoch, c.Epoch)
    45  
    46  	// Processor epoch mismatch
    47  	c.handleHeartbeatResponse(&schedulepb.HeartbeatResponse{
    48  		Liveness: model.LivenessCaptureStopping,
    49  	}, schedulepb.ProcessorEpoch{Epoch: "unknown"})
    50  	require.Equal(t, CaptureStateInitialized, c.State)
    51  
    52  	// Initialized -> Stopping
    53  	c.handleHeartbeatResponse(
    54  		&schedulepb.HeartbeatResponse{Liveness: model.LivenessCaptureStopping}, epoch)
    55  	require.Equal(t, CaptureStateStopping, c.State)
    56  	require.Equal(t, epoch, c.Epoch)
    57  }
    58  
    59  func TestCaptureManagerHandleAliveCaptureUpdate(t *testing.T) {
    60  	t.Parallel()
    61  
    62  	rev := schedulepb.OwnerRevision{}
    63  	cm := NewCaptureManager("1", model.ChangeFeedID{}, rev, config.NewDefaultSchedulerConfig())
    64  	ms := map[model.CaptureID]*model.CaptureInfo{
    65  		"1": {}, "2": {}, "3": {},
    66  	}
    67  
    68  	// Initial handle alive captures.
    69  	msgs := cm.HandleAliveCaptureUpdate(ms)
    70  	require.ElementsMatch(t, []*schedulepb.Message{
    71  		{To: "1", MsgType: schedulepb.MsgHeartbeat, Heartbeat: &schedulepb.Heartbeat{}},
    72  		{To: "2", MsgType: schedulepb.MsgHeartbeat, Heartbeat: &schedulepb.Heartbeat{}},
    73  		{To: "3", MsgType: schedulepb.MsgHeartbeat, Heartbeat: &schedulepb.Heartbeat{}},
    74  	}, msgs)
    75  	require.False(t, cm.CheckAllCaptureInitialized())
    76  	require.Nil(t, cm.TakeChanges())
    77  	require.Contains(t, cm.Captures, "1")
    78  	require.True(t, cm.Captures["1"].IsOwner)
    79  	require.Contains(t, cm.Captures, "2")
    80  	require.False(t, cm.Captures["2"].IsOwner)
    81  	require.Contains(t, cm.Captures, "3")
    82  
    83  	// Remove one capture before init.
    84  	delete(ms, "1")
    85  	msgs = cm.HandleAliveCaptureUpdate(ms)
    86  	require.Len(t, msgs, 0)
    87  	require.Nil(t, cm.TakeChanges())
    88  	require.NotContains(t, cm.Captures, "1")
    89  	require.Contains(t, cm.Captures, "2")
    90  	require.Contains(t, cm.Captures, "3")
    91  
    92  	// Init
    93  	cm.HandleMessage([]*schedulepb.Message{{
    94  		Header: &schedulepb.Message_Header{}, From: "2",
    95  		MsgType: schedulepb.MsgHeartbeatResponse,
    96  		HeartbeatResponse: &schedulepb.HeartbeatResponse{
    97  			Tables: []tablepb.TableStatus{{Span: tablepb.Span{TableID: 1}}},
    98  		},
    99  	}, {
   100  		Header: &schedulepb.Message_Header{}, From: "3",
   101  		MsgType: schedulepb.MsgHeartbeatResponse,
   102  		HeartbeatResponse: &schedulepb.HeartbeatResponse{
   103  			Tables: []tablepb.TableStatus{{Span: tablepb.Span{TableID: 2}}},
   104  		},
   105  	}})
   106  	require.False(t, cm.CheckAllCaptureInitialized())
   107  	msgs = cm.HandleAliveCaptureUpdate(ms)
   108  	require.Len(t, msgs, 0)
   109  	require.True(t, cm.CheckAllCaptureInitialized())
   110  	require.EqualValues(t, &CaptureChanges{
   111  		Init: map[string][]tablepb.TableStatus{
   112  			"2": {{Span: tablepb.Span{TableID: 1}}},
   113  			"3": {{Span: tablepb.Span{TableID: 2}}},
   114  		},
   115  	}, cm.TakeChanges())
   116  
   117  	// Add a new node and remove an old node.
   118  	ms["4"] = &model.CaptureInfo{}
   119  	delete(ms, "2")
   120  	msgs = cm.HandleAliveCaptureUpdate(ms)
   121  	require.ElementsMatch(t, []*schedulepb.Message{
   122  		{To: "4", MsgType: schedulepb.MsgHeartbeat, Heartbeat: &schedulepb.Heartbeat{}},
   123  	}, msgs)
   124  	require.Equal(t, &CaptureChanges{
   125  		Removed: map[string][]tablepb.TableStatus{"2": {{Span: tablepb.Span{TableID: 1}}}},
   126  	}, cm.TakeChanges())
   127  	require.False(t, cm.CheckAllCaptureInitialized())
   128  }
   129  
   130  func TestCaptureManagerHandleMessages(t *testing.T) {
   131  	t.Parallel()
   132  
   133  	rev := schedulepb.OwnerRevision{}
   134  	ms := map[model.CaptureID]*model.CaptureInfo{
   135  		"1": {},
   136  		"2": {},
   137  	}
   138  	cm := NewCaptureManager("", model.ChangeFeedID{}, rev, config.NewDefaultSchedulerConfig())
   139  	require.False(t, cm.CheckAllCaptureInitialized())
   140  
   141  	// Initial handle alive captures.
   142  	msgs := cm.HandleAliveCaptureUpdate(ms)
   143  	require.ElementsMatch(t, []*schedulepb.Message{
   144  		{To: "1", MsgType: schedulepb.MsgHeartbeat, Heartbeat: &schedulepb.Heartbeat{}},
   145  		{To: "2", MsgType: schedulepb.MsgHeartbeat, Heartbeat: &schedulepb.Heartbeat{}},
   146  	}, msgs)
   147  	require.False(t, cm.CheckAllCaptureInitialized())
   148  	require.Contains(t, cm.Captures, "1")
   149  	require.Contains(t, cm.Captures, "2")
   150  
   151  	// Handle one response
   152  	cm.HandleMessage([]*schedulepb.Message{
   153  		{
   154  			Header: &schedulepb.Message_Header{}, From: "1",
   155  			MsgType:           schedulepb.MsgHeartbeatResponse,
   156  			HeartbeatResponse: &schedulepb.HeartbeatResponse{},
   157  		},
   158  	})
   159  	require.False(t, cm.CheckAllCaptureInitialized())
   160  
   161  	// Handle another response
   162  	cm.HandleMessage([]*schedulepb.Message{
   163  		{
   164  			Header: &schedulepb.Message_Header{}, From: "2",
   165  			MsgType:           schedulepb.MsgHeartbeatResponse,
   166  			HeartbeatResponse: &schedulepb.HeartbeatResponse{},
   167  		},
   168  	})
   169  	require.False(t, cm.CheckAllCaptureInitialized(), "%v %v", cm.Captures["1"], cm.Captures["2"])
   170  
   171  	// Handle unknown capture response
   172  	cm.HandleMessage([]*schedulepb.Message{
   173  		{
   174  			Header: &schedulepb.Message_Header{}, From: "unknown",
   175  			MsgType:           schedulepb.MsgHeartbeatResponse,
   176  			HeartbeatResponse: &schedulepb.HeartbeatResponse{},
   177  		},
   178  	})
   179  	require.False(t, cm.CheckAllCaptureInitialized())
   180  }
   181  
   182  func TestCaptureManagerTick(t *testing.T) {
   183  	t.Parallel()
   184  
   185  	rev := schedulepb.OwnerRevision{}
   186  	cm := NewCaptureManager("", model.ChangeFeedID{}, rev, config.NewDefaultSchedulerConfig())
   187  
   188  	// No heartbeat if there is no capture.
   189  	msgs := cm.Tick(spanz.NewBtreeMap[*replication.ReplicationSet](), captureIDNotDraining, nil)
   190  	require.Empty(t, msgs)
   191  	msgs = cm.Tick(spanz.NewBtreeMap[*replication.ReplicationSet](), captureIDNotDraining, nil)
   192  	require.Empty(t, msgs)
   193  
   194  	ms := map[model.CaptureID]*model.CaptureInfo{
   195  		"1": {},
   196  		"2": {},
   197  	}
   198  	cm.HandleAliveCaptureUpdate(ms)
   199  
   200  	// Heartbeat even if capture is uninitialized.
   201  	msgs = cm.Tick(spanz.NewBtreeMap[*replication.ReplicationSet](), captureIDNotDraining, nil)
   202  	require.Empty(t, msgs)
   203  	msgs = cm.Tick(spanz.NewBtreeMap[*replication.ReplicationSet](), captureIDNotDraining, nil)
   204  	require.ElementsMatch(t, []*schedulepb.Message{
   205  		{To: "1", MsgType: schedulepb.MsgHeartbeat, Heartbeat: &schedulepb.Heartbeat{}},
   206  		{To: "2", MsgType: schedulepb.MsgHeartbeat, Heartbeat: &schedulepb.Heartbeat{}},
   207  	}, msgs)
   208  
   209  	// Heartbeat even if capture is initialized or stopping.
   210  	for _, s := range []CaptureState{CaptureStateInitialized, CaptureStateStopping} {
   211  		cm.Captures["1"].State = s
   212  		cm.Captures["2"].State = s
   213  		msgs = cm.Tick(spanz.NewBtreeMap[*replication.ReplicationSet](), captureIDNotDraining, nil)
   214  		require.Empty(t, msgs)
   215  		msgs = cm.Tick(spanz.NewBtreeMap[*replication.ReplicationSet](), captureIDNotDraining, nil)
   216  		require.ElementsMatch(t, []*schedulepb.Message{
   217  			{To: "1", MsgType: schedulepb.MsgHeartbeat, Heartbeat: &schedulepb.Heartbeat{}},
   218  			{To: "2", MsgType: schedulepb.MsgHeartbeat, Heartbeat: &schedulepb.Heartbeat{}},
   219  		}, msgs)
   220  	}
   221  
   222  	// TableID in heartbeat.
   223  	msgs = cm.Tick(spanz.NewBtreeMap[*replication.ReplicationSet](), captureIDNotDraining, nil)
   224  	require.Empty(t, msgs)
   225  
   226  	tables := spanz.NewBtreeMap[*replication.ReplicationSet]()
   227  	tables.ReplaceOrInsert(
   228  		tablepb.Span{TableID: 1},
   229  		&replication.ReplicationSet{Captures: map[model.CaptureID]replication.Role{
   230  			"1": replication.RolePrimary,
   231  		}})
   232  	tables.ReplaceOrInsert(
   233  		tablepb.Span{TableID: 2},
   234  		&replication.ReplicationSet{Captures: map[model.CaptureID]replication.Role{
   235  			"1": replication.RolePrimary, "2": replication.RoleSecondary,
   236  		}})
   237  	tables.ReplaceOrInsert(
   238  		tablepb.Span{TableID: 3},
   239  		&replication.ReplicationSet{Captures: map[model.CaptureID]replication.Role{
   240  			"2": replication.RoleSecondary,
   241  		}})
   242  	tables.ReplaceOrInsert(tablepb.Span{TableID: 4}, &replication.ReplicationSet{})
   243  
   244  	msgs = cm.Tick(tables, captureIDNotDraining, nil)
   245  	require.Len(t, msgs, 2)
   246  	if msgs[0].To == "1" {
   247  		require.ElementsMatch(t,
   248  			[]tablepb.Span{{TableID: 1}, {TableID: 2}}, msgs[0].Heartbeat.Spans)
   249  		require.ElementsMatch(t,
   250  			[]tablepb.Span{{TableID: 2}, {TableID: 3}}, msgs[1].Heartbeat.Spans)
   251  	} else {
   252  		require.ElementsMatch(t,
   253  			[]tablepb.Span{{TableID: 2}, {TableID: 3}}, msgs[0].Heartbeat.Spans)
   254  		require.ElementsMatch(t,
   255  			[]tablepb.Span{{TableID: 1}, {TableID: 2}}, msgs[1].Heartbeat.Spans)
   256  	}
   257  }
   258  
   259  func TestCaptureManagerCollectStatsTick(t *testing.T) {
   260  	t.Parallel()
   261  
   262  	rev := schedulepb.OwnerRevision{}
   263  	cfg := config.NewDefaultSchedulerConfig()
   264  	cfg.HeartbeatTick = 2
   265  	cfg.CollectStatsTick = 3
   266  	cm := NewCaptureManager("", model.ChangeFeedID{}, rev, cfg)
   267  
   268  	ms := map[model.CaptureID]*model.CaptureInfo{
   269  		"1": {},
   270  		"2": {},
   271  	}
   272  	cm.HandleAliveCaptureUpdate(ms)
   273  	cm.SetInitializedForTests(true)
   274  
   275  	// tick      : 1 2 3 4 5 6 7 8
   276  	// heartbeat :   x   x   x   x
   277  	// collect   :     x     x
   278  	for i := 1; i <= 8; i++ {
   279  		msgs := cm.Tick(spanz.NewBtreeMap[*replication.ReplicationSet](), captureIDNotDraining, nil)
   280  		if i%2 == 0 {
   281  			require.Len(t, msgs, 2)
   282  			collect := i == 4 || i == 6
   283  			require.EqualValues(t, msgs[0].Heartbeat.CollectStats, collect, i)
   284  			require.EqualValues(t, msgs[1].Heartbeat.CollectStats, collect, i)
   285  		} else {
   286  			require.Len(t, msgs, 0)
   287  		}
   288  	}
   289  }