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 }