github.com/pingcap/ticdc@v0.0.0-20220526033649-485a10ef2652/cdc/owner/scheduler_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 owner 15 16 import ( 17 "fmt" 18 "math/rand" 19 20 "github.com/pingcap/check" 21 "github.com/pingcap/ticdc/cdc/model" 22 "github.com/pingcap/ticdc/pkg/orchestrator" 23 "github.com/pingcap/ticdc/pkg/util/testleak" 24 ) 25 26 var _ = check.Suite(&schedulerSuite{}) 27 28 type schedulerSuite struct { 29 changefeedID model.ChangeFeedID 30 state *model.ChangefeedReactorState 31 tester *orchestrator.ReactorStateTester 32 captures map[model.CaptureID]*model.CaptureInfo 33 scheduler *scheduler 34 } 35 36 func (s *schedulerSuite) reset(c *check.C) { 37 s.changefeedID = fmt.Sprintf("test-changefeed-%x", rand.Uint32()) 38 s.state = model.NewChangefeedReactorState("test-changefeed") 39 s.tester = orchestrator.NewReactorStateTester(c, s.state, nil) 40 s.scheduler = newScheduler() 41 s.captures = make(map[model.CaptureID]*model.CaptureInfo) 42 s.state.PatchStatus(func(status *model.ChangeFeedStatus) (*model.ChangeFeedStatus, bool, error) { 43 return &model.ChangeFeedStatus{}, true, nil 44 }) 45 s.tester.MustApplyPatches() 46 } 47 48 func (s *schedulerSuite) addCapture(captureID model.CaptureID) { 49 captureInfo := &model.CaptureInfo{ 50 ID: captureID, 51 } 52 s.captures[captureID] = captureInfo 53 s.state.PatchTaskStatus(captureID, func(status *model.TaskStatus) (*model.TaskStatus, bool, error) { 54 return &model.TaskStatus{}, true, nil 55 }) 56 s.tester.MustApplyPatches() 57 } 58 59 func (s *schedulerSuite) finishTableOperation(captureID model.CaptureID, tableIDs ...model.TableID) { 60 s.state.PatchTaskStatus(captureID, func(status *model.TaskStatus) (*model.TaskStatus, bool, error) { 61 for _, tableID := range tableIDs { 62 status.Operation[tableID].Done = true 63 status.Operation[tableID].Status = model.OperFinished 64 } 65 return status, true, nil 66 }) 67 s.state.PatchTaskWorkload(captureID, func(workload model.TaskWorkload) (model.TaskWorkload, bool, error) { 68 if workload == nil { 69 workload = make(model.TaskWorkload) 70 } 71 for _, tableID := range tableIDs { 72 if s.state.TaskStatuses[captureID].Operation[tableID].Delete { 73 delete(workload, tableID) 74 } else { 75 workload[tableID] = model.WorkloadInfo{ 76 Workload: 1, 77 } 78 } 79 } 80 return workload, true, nil 81 }) 82 s.tester.MustApplyPatches() 83 } 84 85 func (s *schedulerSuite) TestScheduleOneCapture(c *check.C) { 86 defer testleak.AfterTest(c)() 87 s.reset(c) 88 captureID := "test-capture-1" 89 s.addCapture(captureID) 90 91 // add three tables 92 shouldUpdateState, err := s.scheduler.Tick(s.state, []model.TableID{1, 2, 3, 4}, s.captures) 93 c.Assert(err, check.IsNil) 94 c.Assert(shouldUpdateState, check.IsFalse) 95 s.tester.MustApplyPatches() 96 c.Assert(s.state.TaskStatuses[captureID].Tables, check.DeepEquals, map[model.TableID]*model.TableReplicaInfo{ 97 1: {StartTs: 0}, 2: {StartTs: 0}, 3: {StartTs: 0}, 4: {StartTs: 0}, 98 }) 99 c.Assert(s.state.TaskStatuses[captureID].Operation, check.DeepEquals, map[model.TableID]*model.TableOperation{ 100 1: {Done: false, Delete: false, BoundaryTs: 0, Status: model.OperDispatched}, 101 2: {Done: false, Delete: false, BoundaryTs: 0, Status: model.OperDispatched}, 102 3: {Done: false, Delete: false, BoundaryTs: 0, Status: model.OperDispatched}, 103 4: {Done: false, Delete: false, BoundaryTs: 0, Status: model.OperDispatched}, 104 }) 105 shouldUpdateState, err = s.scheduler.Tick(s.state, []model.TableID{1, 2, 3, 4}, s.captures) 106 c.Assert(err, check.IsNil) 107 c.Assert(shouldUpdateState, check.IsTrue) 108 s.tester.MustApplyPatches() 109 110 // two tables finish adding operation 111 s.finishTableOperation(captureID, 2, 3) 112 113 // remove table 1,2 and add table 4,5 114 shouldUpdateState, err = s.scheduler.Tick(s.state, []model.TableID{3, 4, 5}, s.captures) 115 c.Assert(err, check.IsNil) 116 c.Assert(shouldUpdateState, check.IsFalse) 117 s.tester.MustApplyPatches() 118 c.Assert(s.state.TaskStatuses[captureID].Tables, check.DeepEquals, map[model.TableID]*model.TableReplicaInfo{ 119 3: {StartTs: 0}, 4: {StartTs: 0}, 5: {StartTs: 0}, 120 }) 121 c.Assert(s.state.TaskStatuses[captureID].Operation, check.DeepEquals, map[model.TableID]*model.TableOperation{ 122 1: {Done: false, Delete: true, BoundaryTs: 0, Status: model.OperDispatched}, 123 2: {Done: false, Delete: true, BoundaryTs: 0, Status: model.OperDispatched}, 124 4: {Done: false, Delete: false, BoundaryTs: 0, Status: model.OperDispatched}, 125 5: {Done: false, Delete: false, BoundaryTs: 0, Status: model.OperDispatched}, 126 }) 127 128 // move a non exist table to a non exist capture 129 s.scheduler.MoveTable(2, "fake-capture") 130 // move tables to a non exist capture 131 s.scheduler.MoveTable(3, "fake-capture") 132 s.scheduler.MoveTable(4, "fake-capture") 133 shouldUpdateState, err = s.scheduler.Tick(s.state, []model.TableID{3, 4, 5}, s.captures) 134 c.Assert(err, check.IsNil) 135 c.Assert(shouldUpdateState, check.IsFalse) 136 s.tester.MustApplyPatches() 137 c.Assert(s.state.TaskStatuses[captureID].Tables, check.DeepEquals, map[model.TableID]*model.TableReplicaInfo{ 138 4: {StartTs: 0}, 5: {StartTs: 0}, 139 }) 140 c.Assert(s.state.TaskStatuses[captureID].Operation, check.DeepEquals, map[model.TableID]*model.TableOperation{ 141 1: {Done: false, Delete: true, BoundaryTs: 0, Status: model.OperDispatched}, 142 2: {Done: false, Delete: true, BoundaryTs: 0, Status: model.OperDispatched}, 143 3: {Done: false, Delete: true, BoundaryTs: 0, Status: model.OperDispatched}, 144 4: {Done: false, Delete: false, BoundaryTs: 0, Status: model.OperDispatched}, 145 5: {Done: false, Delete: false, BoundaryTs: 0, Status: model.OperDispatched}, 146 }) 147 148 // finish all operations 149 s.finishTableOperation(captureID, 1, 2, 3, 4, 5) 150 151 shouldUpdateState, err = s.scheduler.Tick(s.state, []model.TableID{3, 4, 5}, s.captures) 152 c.Assert(err, check.IsNil) 153 c.Assert(shouldUpdateState, check.IsTrue) 154 s.tester.MustApplyPatches() 155 c.Assert(s.state.TaskStatuses[captureID].Tables, check.DeepEquals, map[model.TableID]*model.TableReplicaInfo{ 156 4: {StartTs: 0}, 5: {StartTs: 0}, 157 }) 158 c.Assert(s.state.TaskStatuses[captureID].Operation, check.DeepEquals, map[model.TableID]*model.TableOperation{}) 159 160 // table 3 is missing by expected, because the table was trying to move to a invalid capture 161 // and the move will failed, the table 3 will be add in next tick 162 shouldUpdateState, err = s.scheduler.Tick(s.state, []model.TableID{3, 4, 5}, s.captures) 163 c.Assert(err, check.IsNil) 164 c.Assert(shouldUpdateState, check.IsFalse) 165 s.tester.MustApplyPatches() 166 c.Assert(s.state.TaskStatuses[captureID].Tables, check.DeepEquals, map[model.TableID]*model.TableReplicaInfo{ 167 4: {StartTs: 0}, 5: {StartTs: 0}, 168 }) 169 c.Assert(s.state.TaskStatuses[captureID].Operation, check.DeepEquals, map[model.TableID]*model.TableOperation{}) 170 171 shouldUpdateState, err = s.scheduler.Tick(s.state, []model.TableID{3, 4, 5}, s.captures) 172 c.Assert(err, check.IsNil) 173 c.Assert(shouldUpdateState, check.IsFalse) 174 s.tester.MustApplyPatches() 175 c.Assert(s.state.TaskStatuses[captureID].Tables, check.DeepEquals, map[model.TableID]*model.TableReplicaInfo{ 176 3: {StartTs: 0}, 4: {StartTs: 0}, 5: {StartTs: 0}, 177 }) 178 c.Assert(s.state.TaskStatuses[captureID].Operation, check.DeepEquals, map[model.TableID]*model.TableOperation{ 179 3: {Done: false, Delete: false, BoundaryTs: 0, Status: model.OperDispatched}, 180 }) 181 } 182 183 func (s *schedulerSuite) TestScheduleMoveTable(c *check.C) { 184 defer testleak.AfterTest(c)() 185 s.reset(c) 186 captureID1 := "test-capture-1" 187 captureID2 := "test-capture-2" 188 s.addCapture(captureID1) 189 190 // add a table 191 shouldUpdateState, err := s.scheduler.Tick(s.state, []model.TableID{1}, s.captures) 192 c.Assert(err, check.IsNil) 193 c.Assert(shouldUpdateState, check.IsFalse) 194 s.tester.MustApplyPatches() 195 c.Assert(s.state.TaskStatuses[captureID1].Tables, check.DeepEquals, map[model.TableID]*model.TableReplicaInfo{ 196 1: {StartTs: 0}, 197 }) 198 c.Assert(s.state.TaskStatuses[captureID1].Operation, check.DeepEquals, map[model.TableID]*model.TableOperation{ 199 1: {Done: false, Delete: false, BoundaryTs: 0, Status: model.OperDispatched}, 200 }) 201 202 s.finishTableOperation(captureID1, 1) 203 shouldUpdateState, err = s.scheduler.Tick(s.state, []model.TableID{1}, s.captures) 204 c.Assert(err, check.IsNil) 205 c.Assert(shouldUpdateState, check.IsTrue) 206 s.tester.MustApplyPatches() 207 208 s.addCapture(captureID2) 209 210 // add a table 211 shouldUpdateState, err = s.scheduler.Tick(s.state, []model.TableID{1, 2}, s.captures) 212 c.Assert(err, check.IsNil) 213 c.Assert(shouldUpdateState, check.IsFalse) 214 s.tester.MustApplyPatches() 215 c.Assert(s.state.TaskStatuses[captureID1].Tables, check.DeepEquals, map[model.TableID]*model.TableReplicaInfo{ 216 1: {StartTs: 0}, 217 }) 218 c.Assert(s.state.TaskStatuses[captureID1].Operation, check.DeepEquals, map[model.TableID]*model.TableOperation{}) 219 c.Assert(s.state.TaskStatuses[captureID2].Tables, check.DeepEquals, map[model.TableID]*model.TableReplicaInfo{ 220 2: {StartTs: 0}, 221 }) 222 c.Assert(s.state.TaskStatuses[captureID2].Operation, check.DeepEquals, map[model.TableID]*model.TableOperation{ 223 2: {Done: false, Delete: false, BoundaryTs: 0, Status: model.OperDispatched}, 224 }) 225 226 s.finishTableOperation(captureID2, 2) 227 228 s.scheduler.MoveTable(2, captureID1) 229 shouldUpdateState, err = s.scheduler.Tick(s.state, []model.TableID{1, 2}, s.captures) 230 c.Assert(err, check.IsNil) 231 c.Assert(shouldUpdateState, check.IsFalse) 232 s.tester.MustApplyPatches() 233 c.Assert(s.state.TaskStatuses[captureID1].Tables, check.DeepEquals, map[model.TableID]*model.TableReplicaInfo{ 234 1: {StartTs: 0}, 235 }) 236 c.Assert(s.state.TaskStatuses[captureID1].Operation, check.DeepEquals, map[model.TableID]*model.TableOperation{}) 237 c.Assert(s.state.TaskStatuses[captureID2].Tables, check.DeepEquals, map[model.TableID]*model.TableReplicaInfo{}) 238 c.Assert(s.state.TaskStatuses[captureID2].Operation, check.DeepEquals, map[model.TableID]*model.TableOperation{ 239 2: {Done: false, Delete: true, BoundaryTs: 0, Status: model.OperDispatched}, 240 }) 241 242 s.finishTableOperation(captureID2, 2) 243 244 shouldUpdateState, err = s.scheduler.Tick(s.state, []model.TableID{1, 2}, s.captures) 245 c.Assert(err, check.IsNil) 246 c.Assert(shouldUpdateState, check.IsTrue) 247 s.tester.MustApplyPatches() 248 c.Assert(s.state.TaskStatuses[captureID1].Tables, check.DeepEquals, map[model.TableID]*model.TableReplicaInfo{ 249 1: {StartTs: 0}, 250 }) 251 c.Assert(s.state.TaskStatuses[captureID1].Operation, check.DeepEquals, map[model.TableID]*model.TableOperation{}) 252 c.Assert(s.state.TaskStatuses[captureID2].Tables, check.DeepEquals, map[model.TableID]*model.TableReplicaInfo{}) 253 c.Assert(s.state.TaskStatuses[captureID2].Operation, check.DeepEquals, map[model.TableID]*model.TableOperation{}) 254 255 shouldUpdateState, err = s.scheduler.Tick(s.state, []model.TableID{1, 2}, s.captures) 256 c.Assert(err, check.IsNil) 257 c.Assert(shouldUpdateState, check.IsFalse) 258 s.tester.MustApplyPatches() 259 c.Assert(s.state.TaskStatuses[captureID1].Tables, check.DeepEquals, map[model.TableID]*model.TableReplicaInfo{ 260 1: {StartTs: 0}, 2: {StartTs: 0}, 261 }) 262 c.Assert(s.state.TaskStatuses[captureID1].Operation, check.DeepEquals, map[model.TableID]*model.TableOperation{ 263 2: {Done: false, Delete: false, BoundaryTs: 0, Status: model.OperDispatched}, 264 }) 265 c.Assert(s.state.TaskStatuses[captureID2].Tables, check.DeepEquals, map[model.TableID]*model.TableReplicaInfo{}) 266 c.Assert(s.state.TaskStatuses[captureID2].Operation, check.DeepEquals, map[model.TableID]*model.TableOperation{}) 267 } 268 269 func (s *schedulerSuite) TestScheduleRebalance(c *check.C) { 270 defer testleak.AfterTest(c)() 271 s.reset(c) 272 captureID1 := "test-capture-1" 273 captureID2 := "test-capture-2" 274 captureID3 := "test-capture-3" 275 s.addCapture(captureID1) 276 s.addCapture(captureID2) 277 s.addCapture(captureID3) 278 279 s.state.PatchTaskStatus(captureID1, func(status *model.TaskStatus) (*model.TaskStatus, bool, error) { 280 status.Tables = make(map[model.TableID]*model.TableReplicaInfo) 281 status.Tables[1] = &model.TableReplicaInfo{StartTs: 1} 282 status.Tables[2] = &model.TableReplicaInfo{StartTs: 1} 283 status.Tables[3] = &model.TableReplicaInfo{StartTs: 1} 284 status.Tables[4] = &model.TableReplicaInfo{StartTs: 1} 285 status.Tables[5] = &model.TableReplicaInfo{StartTs: 1} 286 status.Tables[6] = &model.TableReplicaInfo{StartTs: 1} 287 return status, true, nil 288 }) 289 s.tester.MustApplyPatches() 290 291 // rebalance table 292 shouldUpdateState, err := s.scheduler.Tick(s.state, []model.TableID{1, 2, 3, 4, 5, 6}, s.captures) 293 c.Assert(err, check.IsNil) 294 c.Assert(shouldUpdateState, check.IsFalse) 295 s.tester.MustApplyPatches() 296 // 4 tables remove in capture 1, this 4 tables will be added to another capture in next tick 297 c.Assert(s.state.TaskStatuses[captureID1].Tables, check.HasLen, 2) 298 c.Assert(s.state.TaskStatuses[captureID2].Tables, check.HasLen, 0) 299 c.Assert(s.state.TaskStatuses[captureID3].Tables, check.HasLen, 0) 300 301 s.state.PatchTaskStatus(captureID1, func(status *model.TaskStatus) (*model.TaskStatus, bool, error) { 302 for _, opt := range status.Operation { 303 opt.Done = true 304 opt.Status = model.OperFinished 305 } 306 return status, true, nil 307 }) 308 s.state.PatchTaskWorkload(captureID1, func(workload model.TaskWorkload) (model.TaskWorkload, bool, error) { 309 c.Assert(workload, check.IsNil) 310 workload = make(model.TaskWorkload) 311 for tableID := range s.state.TaskStatuses[captureID1].Tables { 312 workload[tableID] = model.WorkloadInfo{Workload: 1} 313 } 314 return workload, true, nil 315 }) 316 s.tester.MustApplyPatches() 317 318 // clean finished operation 319 shouldUpdateState, err = s.scheduler.Tick(s.state, []model.TableID{1, 2, 3, 4, 5, 6}, s.captures) 320 c.Assert(err, check.IsNil) 321 c.Assert(shouldUpdateState, check.IsTrue) 322 s.tester.MustApplyPatches() 323 // 4 tables add to another capture in this tick 324 c.Assert(s.state.TaskStatuses[captureID1].Operation, check.HasLen, 0) 325 326 // rebalance table 327 shouldUpdateState, err = s.scheduler.Tick(s.state, []model.TableID{1, 2, 3, 4, 5, 6}, s.captures) 328 c.Assert(err, check.IsNil) 329 c.Assert(shouldUpdateState, check.IsFalse) 330 s.tester.MustApplyPatches() 331 // 4 tables add to another capture in this tick 332 c.Assert(s.state.TaskStatuses[captureID1].Tables, check.HasLen, 2) 333 c.Assert(s.state.TaskStatuses[captureID2].Tables, check.HasLen, 2) 334 c.Assert(s.state.TaskStatuses[captureID3].Tables, check.HasLen, 2) 335 tableIDs := make(map[model.TableID]struct{}) 336 for _, status := range s.state.TaskStatuses { 337 for tableID := range status.Tables { 338 tableIDs[tableID] = struct{}{} 339 } 340 } 341 c.Assert(tableIDs, check.DeepEquals, map[model.TableID]struct{}{1: {}, 2: {}, 3: {}, 4: {}, 5: {}, 6: {}}) 342 }