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  }