github.com/pingcap/ticdc@v0.0.0-20220526033649-485a10ef2652/cdc/model/reactor_state_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 model
    15  
    16  import (
    17  	"time"
    18  
    19  	"github.com/google/go-cmp/cmp/cmpopts"
    20  
    21  	"github.com/google/go-cmp/cmp"
    22  	"github.com/pingcap/check"
    23  	"github.com/pingcap/ticdc/pkg/config"
    24  	"github.com/pingcap/ticdc/pkg/orchestrator"
    25  	"github.com/pingcap/ticdc/pkg/orchestrator/util"
    26  	"github.com/pingcap/ticdc/pkg/util/testleak"
    27  )
    28  
    29  type stateSuite struct{}
    30  
    31  var _ = check.Suite(&stateSuite{})
    32  
    33  func (s *stateSuite) TestCheckCaptureAlive(c *check.C) {
    34  	defer testleak.AfterTest(c)()
    35  	state := NewChangefeedReactorState("test")
    36  	stateTester := orchestrator.NewReactorStateTester(c, state, nil)
    37  	state.CheckCaptureAlive("6bbc01c8-0605-4f86-a0f9-b3119109b225")
    38  	c.Assert(stateTester.ApplyPatches(), check.ErrorMatches, ".*[CDC:ErrLeaseExpired].*")
    39  	err := stateTester.Update("/tidb/cdc/capture/6bbc01c8-0605-4f86-a0f9-b3119109b225", []byte(`{"id":"6bbc01c8-0605-4f86-a0f9-b3119109b225","address":"127.0.0.1:8300"}`))
    40  	c.Assert(err, check.IsNil)
    41  	state.CheckCaptureAlive("6bbc01c8-0605-4f86-a0f9-b3119109b225")
    42  	stateTester.MustApplyPatches()
    43  }
    44  
    45  func (s *stateSuite) TestChangefeedStateUpdate(c *check.C) {
    46  	defer testleak.AfterTest(c)()
    47  	createTime, err := time.Parse("2006-01-02", "2020-02-02")
    48  	c.Assert(err, check.IsNil)
    49  	testCases := []struct {
    50  		changefeedID string
    51  		updateKey    []string
    52  		updateValue  []string
    53  		expected     ChangefeedReactorState
    54  	}{
    55  		{ // common case
    56  			changefeedID: "test1",
    57  			updateKey: []string{
    58  				"/tidb/cdc/changefeed/info/test1",
    59  				"/tidb/cdc/job/test1",
    60  				"/tidb/cdc/task/position/6bbc01c8-0605-4f86-a0f9-b3119109b225/test1",
    61  				"/tidb/cdc/task/status/6bbc01c8-0605-4f86-a0f9-b3119109b225/test1",
    62  				"/tidb/cdc/task/workload/6bbc01c8-0605-4f86-a0f9-b3119109b225/test1",
    63  				"/tidb/cdc/capture/6bbc01c8-0605-4f86-a0f9-b3119109b225",
    64  			},
    65  			updateValue: []string{
    66  				`{"sink-uri":"blackhole://","opts":{},"create-time":"2020-02-02T00:00:00.000000+00:00","start-ts":421980685886554116,"target-ts":0,"admin-job-type":0,"sort-engine":"memory","sort-dir":"","config":{"case-sensitive":true,"enable-old-value":false,"force-replicate":false,"check-gc-safe-point":true,"filter":{"rules":["*.*"],"ignore-txn-start-ts":null,"ddl-allow-list":null},"mounter":{"worker-num":16},"sink":{"dispatchers":null,"protocol":"default"},"cyclic-replication":{"enable":false,"replica-id":0,"filter-replica-ids":null,"id-buckets":0,"sync-ddl":false},"scheduler":{"type":"table-number","polling-time":-1}},"state":"normal","history":null,"error":null,"sync-point-enabled":false,"sync-point-interval":600000000000}`,
    67  				`{"resolved-ts":421980720003809281,"checkpoint-ts":421980719742451713,"admin-job-type":0}`,
    68  				`{"checkpoint-ts":421980720003809281,"resolved-ts":421980720003809281,"count":0,"error":null}`,
    69  				`{"tables":{"45":{"start-ts":421980685886554116,"mark-table-id":0}},"operation":null,"admin-job-type":0}`,
    70  				`{"45":{"workload":1}}`,
    71  				`{"id":"6bbc01c8-0605-4f86-a0f9-b3119109b225","address":"127.0.0.1:8300"}`,
    72  			},
    73  			expected: ChangefeedReactorState{
    74  				ID: "test1",
    75  				Info: &ChangeFeedInfo{
    76  					SinkURI:           "blackhole://",
    77  					Opts:              map[string]string{},
    78  					CreateTime:        createTime,
    79  					StartTs:           421980685886554116,
    80  					Engine:            SortInMemory,
    81  					State:             "normal",
    82  					SyncPointInterval: time.Minute * 10,
    83  					Config: &config.ReplicaConfig{
    84  						CaseSensitive:    true,
    85  						CheckGCSafePoint: true,
    86  						Filter:           &config.FilterConfig{Rules: []string{"*.*"}},
    87  						Mounter:          &config.MounterConfig{WorkerNum: 16},
    88  						Sink:             &config.SinkConfig{Protocol: "default"},
    89  						Cyclic:           &config.CyclicConfig{},
    90  						Scheduler:        &config.SchedulerConfig{Tp: "table-number", PollingTime: -1},
    91  					},
    92  				},
    93  				Status: &ChangeFeedStatus{CheckpointTs: 421980719742451713, ResolvedTs: 421980720003809281},
    94  				TaskStatuses: map[CaptureID]*TaskStatus{
    95  					"6bbc01c8-0605-4f86-a0f9-b3119109b225": {
    96  						Tables: map[int64]*TableReplicaInfo{45: {StartTs: 421980685886554116}},
    97  					},
    98  				},
    99  				TaskPositions: map[CaptureID]*TaskPosition{
   100  					"6bbc01c8-0605-4f86-a0f9-b3119109b225": {CheckPointTs: 421980720003809281, ResolvedTs: 421980720003809281},
   101  				},
   102  				Workloads: map[CaptureID]TaskWorkload{
   103  					"6bbc01c8-0605-4f86-a0f9-b3119109b225": {45: {Workload: 1}},
   104  				},
   105  			},
   106  		},
   107  		{ // test multiple capture
   108  			changefeedID: "test1",
   109  			updateKey: []string{
   110  				"/tidb/cdc/changefeed/info/test1",
   111  				"/tidb/cdc/job/test1",
   112  				"/tidb/cdc/task/position/6bbc01c8-0605-4f86-a0f9-b3119109b225/test1",
   113  				"/tidb/cdc/task/status/6bbc01c8-0605-4f86-a0f9-b3119109b225/test1",
   114  				"/tidb/cdc/task/workload/6bbc01c8-0605-4f86-a0f9-b3119109b225/test1",
   115  				"/tidb/cdc/capture/6bbc01c8-0605-4f86-a0f9-b3119109b225",
   116  				"/tidb/cdc/task/position/666777888/test1",
   117  				"/tidb/cdc/task/status/666777888/test1",
   118  				"/tidb/cdc/task/workload/666777888/test1",
   119  				"/tidb/cdc/capture/666777888",
   120  			},
   121  			updateValue: []string{
   122  				`{"sink-uri":"blackhole://","opts":{},"create-time":"2020-02-02T00:00:00.000000+00:00","start-ts":421980685886554116,"target-ts":0,"admin-job-type":0,"sort-engine":"memory","sort-dir":"","config":{"case-sensitive":true,"enable-old-value":false,"force-replicate":false,"check-gc-safe-point":true,"filter":{"rules":["*.*"],"ignore-txn-start-ts":null,"ddl-allow-list":null},"mounter":{"worker-num":16},"sink":{"dispatchers":null,"protocol":"default"},"cyclic-replication":{"enable":false,"replica-id":0,"filter-replica-ids":null,"id-buckets":0,"sync-ddl":false},"scheduler":{"type":"table-number","polling-time":-1}},"state":"normal","history":null,"error":null,"sync-point-enabled":false,"sync-point-interval":600000000000}`,
   123  				`{"resolved-ts":421980720003809281,"checkpoint-ts":421980719742451713,"admin-job-type":0}`,
   124  				`{"checkpoint-ts":421980720003809281,"resolved-ts":421980720003809281,"count":0,"error":null}`,
   125  				`{"tables":{"45":{"start-ts":421980685886554116,"mark-table-id":0}},"operation":null,"admin-job-type":0}`,
   126  				`{"45":{"workload":1}}`,
   127  				`{"id":"6bbc01c8-0605-4f86-a0f9-b3119109b225","address":"127.0.0.1:8300"}`,
   128  				`{"checkpoint-ts":11332244,"resolved-ts":312321,"count":8,"error":null}`,
   129  				`{"tables":{"46":{"start-ts":412341234,"mark-table-id":0}},"operation":null,"admin-job-type":0}`,
   130  				`{"46":{"workload":3}}`,
   131  				`{"id":"666777888","address":"127.0.0.1:8300"}`,
   132  			},
   133  			expected: ChangefeedReactorState{
   134  				ID: "test1",
   135  				Info: &ChangeFeedInfo{
   136  					SinkURI:           "blackhole://",
   137  					Opts:              map[string]string{},
   138  					CreateTime:        createTime,
   139  					StartTs:           421980685886554116,
   140  					Engine:            SortInMemory,
   141  					State:             "normal",
   142  					SyncPointInterval: time.Minute * 10,
   143  					Config: &config.ReplicaConfig{
   144  						CaseSensitive:    true,
   145  						CheckGCSafePoint: true,
   146  						Filter:           &config.FilterConfig{Rules: []string{"*.*"}},
   147  						Mounter:          &config.MounterConfig{WorkerNum: 16},
   148  						Sink:             &config.SinkConfig{Protocol: "default"},
   149  						Cyclic:           &config.CyclicConfig{},
   150  						Scheduler:        &config.SchedulerConfig{Tp: "table-number", PollingTime: -1},
   151  					},
   152  				},
   153  				Status: &ChangeFeedStatus{CheckpointTs: 421980719742451713, ResolvedTs: 421980720003809281},
   154  				TaskStatuses: map[CaptureID]*TaskStatus{
   155  					"6bbc01c8-0605-4f86-a0f9-b3119109b225": {
   156  						Tables: map[int64]*TableReplicaInfo{45: {StartTs: 421980685886554116}},
   157  					},
   158  					"666777888": {
   159  						Tables: map[int64]*TableReplicaInfo{46: {StartTs: 412341234}},
   160  					},
   161  				},
   162  				TaskPositions: map[CaptureID]*TaskPosition{
   163  					"6bbc01c8-0605-4f86-a0f9-b3119109b225": {CheckPointTs: 421980720003809281, ResolvedTs: 421980720003809281},
   164  					"666777888":                            {CheckPointTs: 11332244, ResolvedTs: 312321, Count: 8},
   165  				},
   166  				Workloads: map[CaptureID]TaskWorkload{
   167  					"6bbc01c8-0605-4f86-a0f9-b3119109b225": {45: {Workload: 1}},
   168  					"666777888":                            {46: {Workload: 3}},
   169  				},
   170  			},
   171  		},
   172  		{ // testing changefeedID not match
   173  			changefeedID: "test1",
   174  			updateKey: []string{
   175  				"/tidb/cdc/changefeed/info/test1",
   176  				"/tidb/cdc/job/test1",
   177  				"/tidb/cdc/task/position/6bbc01c8-0605-4f86-a0f9-b3119109b225/test1",
   178  				"/tidb/cdc/task/status/6bbc01c8-0605-4f86-a0f9-b3119109b225/test1",
   179  				"/tidb/cdc/task/workload/6bbc01c8-0605-4f86-a0f9-b3119109b225/test1",
   180  				"/tidb/cdc/capture/6bbc01c8-0605-4f86-a0f9-b3119109b225",
   181  				"/tidb/cdc/changefeed/info/test-fake",
   182  				"/tidb/cdc/job/test-fake",
   183  				"/tidb/cdc/task/position/6bbc01c8-0605-4f86-a0f9-b3119109b225/test-fake",
   184  				"/tidb/cdc/task/status/6bbc01c8-0605-4f86-a0f9-b3119109b225/test-fake",
   185  				"/tidb/cdc/task/workload/6bbc01c8-0605-4f86-a0f9-b3119109b225/test-fake",
   186  			},
   187  			updateValue: []string{
   188  				`{"sink-uri":"blackhole://","opts":{},"create-time":"2020-02-02T00:00:00.000000+00:00","start-ts":421980685886554116,"target-ts":0,"admin-job-type":0,"sort-engine":"memory","sort-dir":"","config":{"case-sensitive":true,"enable-old-value":false,"force-replicate":false,"check-gc-safe-point":true,"filter":{"rules":["*.*"],"ignore-txn-start-ts":null,"ddl-allow-list":null},"mounter":{"worker-num":16},"sink":{"dispatchers":null,"protocol":"default"},"cyclic-replication":{"enable":false,"replica-id":0,"filter-replica-ids":null,"id-buckets":0,"sync-ddl":false},"scheduler":{"type":"table-number","polling-time":-1}},"state":"normal","history":null,"error":null,"sync-point-enabled":false,"sync-point-interval":600000000000}`,
   189  				`{"resolved-ts":421980720003809281,"checkpoint-ts":421980719742451713,"admin-job-type":0}`,
   190  				`{"checkpoint-ts":421980720003809281,"resolved-ts":421980720003809281,"count":0,"error":null}`,
   191  				`{"tables":{"45":{"start-ts":421980685886554116,"mark-table-id":0}},"operation":null,"admin-job-type":0}`,
   192  				`{"45":{"workload":1}}`,
   193  				`{"id":"6bbc01c8-0605-4f86-a0f9-b3119109b225","address":"127.0.0.1:8300"}`,
   194  				`fake value`,
   195  				`fake value`,
   196  				`fake value`,
   197  				`fake value`,
   198  				`fake value`,
   199  			},
   200  			expected: ChangefeedReactorState{
   201  				ID: "test1",
   202  				Info: &ChangeFeedInfo{
   203  					SinkURI:           "blackhole://",
   204  					Opts:              map[string]string{},
   205  					CreateTime:        createTime,
   206  					StartTs:           421980685886554116,
   207  					Engine:            SortInMemory,
   208  					State:             "normal",
   209  					SyncPointInterval: time.Minute * 10,
   210  					Config: &config.ReplicaConfig{
   211  						CaseSensitive:    true,
   212  						CheckGCSafePoint: true,
   213  						Filter:           &config.FilterConfig{Rules: []string{"*.*"}},
   214  						Mounter:          &config.MounterConfig{WorkerNum: 16},
   215  						Sink:             &config.SinkConfig{Protocol: "default"},
   216  						Cyclic:           &config.CyclicConfig{},
   217  						Scheduler:        &config.SchedulerConfig{Tp: "table-number", PollingTime: -1},
   218  					},
   219  				},
   220  				Status: &ChangeFeedStatus{CheckpointTs: 421980719742451713, ResolvedTs: 421980720003809281},
   221  				TaskStatuses: map[CaptureID]*TaskStatus{
   222  					"6bbc01c8-0605-4f86-a0f9-b3119109b225": {
   223  						Tables: map[int64]*TableReplicaInfo{45: {StartTs: 421980685886554116}},
   224  					},
   225  				},
   226  				TaskPositions: map[CaptureID]*TaskPosition{
   227  					"6bbc01c8-0605-4f86-a0f9-b3119109b225": {CheckPointTs: 421980720003809281, ResolvedTs: 421980720003809281},
   228  				},
   229  				Workloads: map[CaptureID]TaskWorkload{
   230  					"6bbc01c8-0605-4f86-a0f9-b3119109b225": {45: {Workload: 1}},
   231  				},
   232  			},
   233  		},
   234  		{ // testing value is nil
   235  			changefeedID: "test1",
   236  			updateKey: []string{
   237  				"/tidb/cdc/changefeed/info/test1",
   238  				"/tidb/cdc/job/test1",
   239  				"/tidb/cdc/task/position/6bbc01c8-0605-4f86-a0f9-b3119109b225/test1",
   240  				"/tidb/cdc/task/status/6bbc01c8-0605-4f86-a0f9-b3119109b225/test1",
   241  				"/tidb/cdc/task/workload/6bbc01c8-0605-4f86-a0f9-b3119109b225/test1",
   242  				"/tidb/cdc/capture/6bbc01c8-0605-4f86-a0f9-b3119109b225",
   243  				"/tidb/cdc/task/position/666777888/test1",
   244  				"/tidb/cdc/task/status/666777888/test1",
   245  				"/tidb/cdc/task/workload/666777888/test1",
   246  				"/tidb/cdc/changefeed/info/test1",
   247  				"/tidb/cdc/job/test1",
   248  				"/tidb/cdc/task/position/6bbc01c8-0605-4f86-a0f9-b3119109b225/test1",
   249  				"/tidb/cdc/task/status/6bbc01c8-0605-4f86-a0f9-b3119109b225/test1",
   250  				"/tidb/cdc/task/workload/6bbc01c8-0605-4f86-a0f9-b3119109b225/test1",
   251  				"/tidb/cdc/capture/6bbc01c8-0605-4f86-a0f9-b3119109b225",
   252  				"/tidb/cdc/task/workload/666777888/test1",
   253  				"/tidb/cdc/task/status/666777888/test1",
   254  			},
   255  			updateValue: []string{
   256  				`{"sink-uri":"blackhole://","opts":{},"create-time":"2020-02-02T00:00:00.000000+00:00","start-ts":421980685886554116,"target-ts":0,"admin-job-type":0,"sort-engine":"memory","sort-dir":"","config":{"case-sensitive":true,"enable-old-value":false,"force-replicate":false,"check-gc-safe-point":true,"filter":{"rules":["*.*"],"ignore-txn-start-ts":null,"ddl-allow-list":null},"mounter":{"worker-num":16},"sink":{"dispatchers":null,"protocol":"default"},"cyclic-replication":{"enable":false,"replica-id":0,"filter-replica-ids":null,"id-buckets":0,"sync-ddl":false},"scheduler":{"type":"table-number","polling-time":-1}},"state":"normal","history":null,"error":null,"sync-point-enabled":false,"sync-point-interval":600000000000}`,
   257  				`{"resolved-ts":421980720003809281,"checkpoint-ts":421980719742451713,"admin-job-type":0}`,
   258  				`{"checkpoint-ts":421980720003809281,"resolved-ts":421980720003809281,"count":0,"error":null}`,
   259  				`{"tables":{"45":{"start-ts":421980685886554116,"mark-table-id":0}},"operation":null,"admin-job-type":0}`,
   260  				`{"45":{"workload":1}}`,
   261  				`{"id":"6bbc01c8-0605-4f86-a0f9-b3119109b225","address":"127.0.0.1:8300"}`,
   262  				`{"checkpoint-ts":11332244,"resolved-ts":312321,"count":8,"error":null}`,
   263  				`{"tables":{"46":{"start-ts":412341234,"mark-table-id":0}},"operation":null,"admin-job-type":0}`,
   264  				`{"46":{"workload":3}}`,
   265  				``,
   266  				``,
   267  				``,
   268  				``,
   269  				``,
   270  				``,
   271  				``,
   272  				``,
   273  			},
   274  			expected: ChangefeedReactorState{
   275  				ID:           "test1",
   276  				Info:         nil,
   277  				Status:       nil,
   278  				TaskStatuses: map[CaptureID]*TaskStatus{},
   279  				TaskPositions: map[CaptureID]*TaskPosition{
   280  					"666777888": {CheckPointTs: 11332244, ResolvedTs: 312321, Count: 8},
   281  				},
   282  				Workloads: map[CaptureID]TaskWorkload{},
   283  			},
   284  		},
   285  		{ // testing the same key case
   286  			changefeedID: "test1",
   287  			updateKey: []string{
   288  				"/tidb/cdc/task/status/6bbc01c8-0605-4f86-a0f9-b3119109b225/test1",
   289  				"/tidb/cdc/task/status/6bbc01c8-0605-4f86-a0f9-b3119109b225/test1",
   290  				"/tidb/cdc/task/status/6bbc01c8-0605-4f86-a0f9-b3119109b225/test1",
   291  				"/tidb/cdc/task/status/6bbc01c8-0605-4f86-a0f9-b3119109b225/test1",
   292  			},
   293  			updateValue: []string{
   294  				`{"tables":{"45":{"start-ts":421980685886554116,"mark-table-id":0}},"operation":null,"admin-job-type":0}`,
   295  				`{"tables":{"46":{"start-ts":421980685886554116,"mark-table-id":0}},"operation":null,"admin-job-type":0}`,
   296  				``,
   297  				`{"tables":{"47":{"start-ts":421980685886554116,"mark-table-id":0}},"operation":null,"admin-job-type":0}`,
   298  			},
   299  			expected: ChangefeedReactorState{
   300  				ID: "test1",
   301  				TaskStatuses: map[CaptureID]*TaskStatus{
   302  					"6bbc01c8-0605-4f86-a0f9-b3119109b225": {
   303  						Tables: map[int64]*TableReplicaInfo{47: {StartTs: 421980685886554116}},
   304  					},
   305  				},
   306  				TaskPositions: map[CaptureID]*TaskPosition{},
   307  				Workloads:     map[CaptureID]TaskWorkload{},
   308  			},
   309  		},
   310  	}
   311  	for i, tc := range testCases {
   312  		state := NewChangefeedReactorState(tc.changefeedID)
   313  		for i, k := range tc.updateKey {
   314  			value := []byte(tc.updateValue[i])
   315  			if len(value) == 0 {
   316  				value = nil
   317  			}
   318  			err = state.Update(util.NewEtcdKey(k), value, false)
   319  			c.Assert(err, check.IsNil)
   320  		}
   321  		c.Assert(cmp.Equal(state, &tc.expected, cmpopts.IgnoreUnexported(ChangefeedReactorState{})), check.IsTrue,
   322  			check.Commentf("%d,%s", i, cmp.Diff(state, &tc.expected, cmpopts.IgnoreUnexported(ChangefeedReactorState{}))))
   323  	}
   324  }
   325  
   326  func (s *stateSuite) TestPatchInfo(c *check.C) {
   327  	defer testleak.AfterTest(c)()
   328  	state := NewChangefeedReactorState("test1")
   329  	stateTester := orchestrator.NewReactorStateTester(c, state, nil)
   330  	state.PatchInfo(func(info *ChangeFeedInfo) (*ChangeFeedInfo, bool, error) {
   331  		c.Assert(info, check.IsNil)
   332  		return &ChangeFeedInfo{SinkURI: "123", Config: &config.ReplicaConfig{}}, true, nil
   333  	})
   334  	stateTester.MustApplyPatches()
   335  	defaultConfig := config.GetDefaultReplicaConfig()
   336  	c.Assert(state.Info, check.DeepEquals, &ChangeFeedInfo{
   337  		SinkURI: "123",
   338  		Engine:  SortUnified,
   339  		Config: &config.ReplicaConfig{
   340  			Filter:    defaultConfig.Filter,
   341  			Mounter:   defaultConfig.Mounter,
   342  			Sink:      defaultConfig.Sink,
   343  			Cyclic:    defaultConfig.Cyclic,
   344  			Scheduler: defaultConfig.Scheduler,
   345  		},
   346  	})
   347  	state.PatchInfo(func(info *ChangeFeedInfo) (*ChangeFeedInfo, bool, error) {
   348  		info.StartTs = 6
   349  		return info, true, nil
   350  	})
   351  	stateTester.MustApplyPatches()
   352  	c.Assert(state.Info, check.DeepEquals, &ChangeFeedInfo{
   353  		SinkURI: "123",
   354  		StartTs: 6,
   355  		Engine:  SortUnified,
   356  		Config: &config.ReplicaConfig{
   357  			Filter:    defaultConfig.Filter,
   358  			Mounter:   defaultConfig.Mounter,
   359  			Sink:      defaultConfig.Sink,
   360  			Cyclic:    defaultConfig.Cyclic,
   361  			Scheduler: defaultConfig.Scheduler,
   362  		},
   363  	})
   364  	state.PatchInfo(func(info *ChangeFeedInfo) (*ChangeFeedInfo, bool, error) {
   365  		return nil, true, nil
   366  	})
   367  	stateTester.MustApplyPatches()
   368  	c.Assert(state.Info, check.IsNil)
   369  }
   370  
   371  func (s *stateSuite) TestPatchStatus(c *check.C) {
   372  	defer testleak.AfterTest(c)()
   373  	state := NewChangefeedReactorState("test1")
   374  	stateTester := orchestrator.NewReactorStateTester(c, state, nil)
   375  	state.PatchStatus(func(status *ChangeFeedStatus) (*ChangeFeedStatus, bool, error) {
   376  		c.Assert(status, check.IsNil)
   377  		return &ChangeFeedStatus{CheckpointTs: 5}, true, nil
   378  	})
   379  	stateTester.MustApplyPatches()
   380  	c.Assert(state.Status, check.DeepEquals, &ChangeFeedStatus{CheckpointTs: 5})
   381  	state.PatchStatus(func(status *ChangeFeedStatus) (*ChangeFeedStatus, bool, error) {
   382  		status.ResolvedTs = 6
   383  		return status, true, nil
   384  	})
   385  	stateTester.MustApplyPatches()
   386  	c.Assert(state.Status, check.DeepEquals, &ChangeFeedStatus{CheckpointTs: 5, ResolvedTs: 6})
   387  	state.PatchStatus(func(status *ChangeFeedStatus) (*ChangeFeedStatus, bool, error) {
   388  		return nil, true, nil
   389  	})
   390  	stateTester.MustApplyPatches()
   391  	c.Assert(state.Status, check.IsNil)
   392  }
   393  
   394  func (s *stateSuite) TestPatchTaskPosition(c *check.C) {
   395  	defer testleak.AfterTest(c)()
   396  	state := NewChangefeedReactorState("test1")
   397  	stateTester := orchestrator.NewReactorStateTester(c, state, nil)
   398  	captureID1 := "capture1"
   399  	captureID2 := "capture2"
   400  	state.PatchTaskPosition(captureID1, func(position *TaskPosition) (*TaskPosition, bool, error) {
   401  		c.Assert(position, check.IsNil)
   402  		return &TaskPosition{
   403  			CheckPointTs: 1,
   404  		}, true, nil
   405  	})
   406  	state.PatchTaskPosition(captureID2, func(position *TaskPosition) (*TaskPosition, bool, error) {
   407  		c.Assert(position, check.IsNil)
   408  		return &TaskPosition{
   409  			CheckPointTs: 2,
   410  		}, true, nil
   411  	})
   412  	stateTester.MustApplyPatches()
   413  	c.Assert(state.TaskPositions, check.DeepEquals, map[string]*TaskPosition{
   414  		captureID1: {
   415  			CheckPointTs: 1,
   416  		},
   417  		captureID2: {
   418  			CheckPointTs: 2,
   419  		},
   420  	})
   421  	state.PatchTaskPosition(captureID1, func(position *TaskPosition) (*TaskPosition, bool, error) {
   422  		position.CheckPointTs = 3
   423  		return position, true, nil
   424  	})
   425  	state.PatchTaskPosition(captureID2, func(position *TaskPosition) (*TaskPosition, bool, error) {
   426  		position.ResolvedTs = 2
   427  		return position, true, nil
   428  	})
   429  	stateTester.MustApplyPatches()
   430  	c.Assert(state.TaskPositions, check.DeepEquals, map[string]*TaskPosition{
   431  		captureID1: {
   432  			CheckPointTs: 3,
   433  		},
   434  		captureID2: {
   435  			CheckPointTs: 2,
   436  			ResolvedTs:   2,
   437  		},
   438  	})
   439  	state.PatchTaskPosition(captureID1, func(position *TaskPosition) (*TaskPosition, bool, error) {
   440  		return nil, false, nil
   441  	})
   442  	state.PatchTaskPosition(captureID2, func(position *TaskPosition) (*TaskPosition, bool, error) {
   443  		return nil, true, nil
   444  	})
   445  	state.PatchTaskPosition(captureID1, func(position *TaskPosition) (*TaskPosition, bool, error) {
   446  		position.Count = 6
   447  		return position, true, nil
   448  	})
   449  	stateTester.MustApplyPatches()
   450  	c.Assert(state.TaskPositions, check.DeepEquals, map[string]*TaskPosition{
   451  		captureID1: {
   452  			CheckPointTs: 3,
   453  			Count:        6,
   454  		},
   455  	})
   456  }
   457  
   458  func (s *stateSuite) TestPatchTaskStatus(c *check.C) {
   459  	defer testleak.AfterTest(c)()
   460  	state := NewChangefeedReactorState("test1")
   461  	stateTester := orchestrator.NewReactorStateTester(c, state, nil)
   462  	captureID1 := "capture1"
   463  	captureID2 := "capture2"
   464  	state.PatchTaskStatus(captureID1, func(status *TaskStatus) (*TaskStatus, bool, error) {
   465  		c.Assert(status, check.IsNil)
   466  		return &TaskStatus{
   467  			Tables: map[TableID]*TableReplicaInfo{45: {StartTs: 1}},
   468  		}, true, nil
   469  	})
   470  	state.PatchTaskStatus(captureID2, func(status *TaskStatus) (*TaskStatus, bool, error) {
   471  		c.Assert(status, check.IsNil)
   472  		return &TaskStatus{
   473  			Tables: map[TableID]*TableReplicaInfo{46: {StartTs: 1}},
   474  		}, true, nil
   475  	})
   476  	stateTester.MustApplyPatches()
   477  	c.Assert(state.TaskStatuses, check.DeepEquals, map[CaptureID]*TaskStatus{
   478  		captureID1: {Tables: map[TableID]*TableReplicaInfo{45: {StartTs: 1}}},
   479  		captureID2: {Tables: map[TableID]*TableReplicaInfo{46: {StartTs: 1}}},
   480  	})
   481  	state.PatchTaskStatus(captureID1, func(status *TaskStatus) (*TaskStatus, bool, error) {
   482  		status.Tables[46] = &TableReplicaInfo{StartTs: 2}
   483  		return status, true, nil
   484  	})
   485  	state.PatchTaskStatus(captureID2, func(status *TaskStatus) (*TaskStatus, bool, error) {
   486  		status.Tables[46].StartTs++
   487  		return status, true, nil
   488  	})
   489  	stateTester.MustApplyPatches()
   490  	c.Assert(state.TaskStatuses, check.DeepEquals, map[CaptureID]*TaskStatus{
   491  		captureID1: {Tables: map[TableID]*TableReplicaInfo{45: {StartTs: 1}, 46: {StartTs: 2}}},
   492  		captureID2: {Tables: map[TableID]*TableReplicaInfo{46: {StartTs: 2}}},
   493  	})
   494  	state.PatchTaskStatus(captureID2, func(status *TaskStatus) (*TaskStatus, bool, error) {
   495  		return nil, true, nil
   496  	})
   497  	stateTester.MustApplyPatches()
   498  	c.Assert(state.TaskStatuses, check.DeepEquals, map[CaptureID]*TaskStatus{
   499  		captureID1: {Tables: map[TableID]*TableReplicaInfo{45: {StartTs: 1}, 46: {StartTs: 2}}},
   500  	})
   501  }
   502  
   503  func (s *stateSuite) TestPatchTaskWorkload(c *check.C) {
   504  	defer testleak.AfterTest(c)()
   505  	state := NewChangefeedReactorState("test1")
   506  	stateTester := orchestrator.NewReactorStateTester(c, state, nil)
   507  	captureID1 := "capture1"
   508  	captureID2 := "capture2"
   509  	state.PatchTaskWorkload(captureID1, func(workload TaskWorkload) (TaskWorkload, bool, error) {
   510  		c.Assert(workload, check.IsNil)
   511  		return TaskWorkload{45: {Workload: 1}}, true, nil
   512  	})
   513  	state.PatchTaskWorkload(captureID2, func(workload TaskWorkload) (TaskWorkload, bool, error) {
   514  		c.Assert(workload, check.IsNil)
   515  		return TaskWorkload{46: {Workload: 1}}, true, nil
   516  	})
   517  	stateTester.MustApplyPatches()
   518  	c.Assert(state.Workloads, check.DeepEquals, map[CaptureID]TaskWorkload{
   519  		captureID1: {45: {Workload: 1}},
   520  		captureID2: {46: {Workload: 1}},
   521  	})
   522  	state.PatchTaskWorkload(captureID1, func(workload TaskWorkload) (TaskWorkload, bool, error) {
   523  		workload[46] = WorkloadInfo{Workload: 2}
   524  		return workload, true, nil
   525  	})
   526  	state.PatchTaskWorkload(captureID2, func(workload TaskWorkload) (TaskWorkload, bool, error) {
   527  		workload[45] = WorkloadInfo{Workload: 3}
   528  		return workload, true, nil
   529  	})
   530  	stateTester.MustApplyPatches()
   531  	c.Assert(state.Workloads, check.DeepEquals, map[CaptureID]TaskWorkload{
   532  		captureID1: {45: {Workload: 1}, 46: {Workload: 2}},
   533  		captureID2: {45: {Workload: 3}, 46: {Workload: 1}},
   534  	})
   535  	state.PatchTaskWorkload(captureID2, func(workload TaskWorkload) (TaskWorkload, bool, error) {
   536  		return nil, true, nil
   537  	})
   538  	stateTester.MustApplyPatches()
   539  	c.Assert(state.Workloads, check.DeepEquals, map[CaptureID]TaskWorkload{
   540  		captureID1: {45: {Workload: 1}, 46: {Workload: 2}},
   541  	})
   542  }
   543  
   544  func (s *stateSuite) TestGlobalStateUpdate(c *check.C) {
   545  	defer testleak.AfterTest(c)()
   546  	testCases := []struct {
   547  		updateKey   []string
   548  		updateValue []string
   549  		expected    GlobalReactorState
   550  	}{
   551  		{ // common case
   552  			updateKey: []string{
   553  				"/tidb/cdc/owner/22317526c4fc9a37",
   554  				"/tidb/cdc/owner/22317526c4fc9a38",
   555  				"/tidb/cdc/capture/6bbc01c8-0605-4f86-a0f9-b3119109b225",
   556  				"/tidb/cdc/task/position/6bbc01c8-0605-4f86-a0f9-b3119109b225/test1",
   557  				"/tidb/cdc/task/workload/6bbc01c8-0605-4f86-a0f9-b3119109b225/test2",
   558  				"/tidb/cdc/task/workload/55551111/test2",
   559  			},
   560  			updateValue: []string{
   561  				`6bbc01c8-0605-4f86-a0f9-b3119109b225`,
   562  				`55551111`,
   563  				`{"id":"6bbc01c8-0605-4f86-a0f9-b3119109b225","address":"127.0.0.1:8300"}`,
   564  				`{"resolved-ts":421980720003809281,"checkpoint-ts":421980719742451713,"admin-job-type":0}`,
   565  				`{"45":{"workload":1}}`,
   566  				`{"46":{"workload":1}}`,
   567  			},
   568  			expected: GlobalReactorState{
   569  				Owner: map[string]struct{}{"22317526c4fc9a37": {}, "22317526c4fc9a38": {}},
   570  				Captures: map[CaptureID]*CaptureInfo{"6bbc01c8-0605-4f86-a0f9-b3119109b225": {
   571  					ID:            "6bbc01c8-0605-4f86-a0f9-b3119109b225",
   572  					AdvertiseAddr: "127.0.0.1:8300",
   573  				}},
   574  				Changefeeds: map[ChangeFeedID]*ChangefeedReactorState{
   575  					"test1": {
   576  						ID:           "test1",
   577  						TaskStatuses: map[string]*TaskStatus{},
   578  						TaskPositions: map[CaptureID]*TaskPosition{
   579  							"6bbc01c8-0605-4f86-a0f9-b3119109b225": {CheckPointTs: 421980719742451713, ResolvedTs: 421980720003809281},
   580  						},
   581  						Workloads: map[string]TaskWorkload{},
   582  					},
   583  					"test2": {
   584  						ID:            "test2",
   585  						TaskStatuses:  map[string]*TaskStatus{},
   586  						TaskPositions: map[CaptureID]*TaskPosition{},
   587  						Workloads: map[CaptureID]TaskWorkload{
   588  							"6bbc01c8-0605-4f86-a0f9-b3119109b225": {45: {Workload: 1}},
   589  							"55551111":                             {46: {Workload: 1}},
   590  						},
   591  					},
   592  				},
   593  			},
   594  		},
   595  		{ // testing remove changefeed
   596  			updateKey: []string{
   597  				"/tidb/cdc/owner/22317526c4fc9a37",
   598  				"/tidb/cdc/owner/22317526c4fc9a38",
   599  				"/tidb/cdc/capture/6bbc01c8-0605-4f86-a0f9-b3119109b225",
   600  				"/tidb/cdc/task/position/6bbc01c8-0605-4f86-a0f9-b3119109b225/test1",
   601  				"/tidb/cdc/task/workload/6bbc01c8-0605-4f86-a0f9-b3119109b225/test2",
   602  				"/tidb/cdc/task/workload/55551111/test2",
   603  				"/tidb/cdc/owner/22317526c4fc9a37",
   604  				"/tidb/cdc/task/position/6bbc01c8-0605-4f86-a0f9-b3119109b225/test1",
   605  				"/tidb/cdc/task/workload/6bbc01c8-0605-4f86-a0f9-b3119109b225/test2",
   606  				"/tidb/cdc/capture/6bbc01c8-0605-4f86-a0f9-b3119109b225",
   607  			},
   608  			updateValue: []string{
   609  				`6bbc01c8-0605-4f86-a0f9-b3119109b225`,
   610  				`55551111`,
   611  				`{"id":"6bbc01c8-0605-4f86-a0f9-b3119109b225","address":"127.0.0.1:8300"}`,
   612  				`{"resolved-ts":421980720003809281,"checkpoint-ts":421980719742451713,"admin-job-type":0}`,
   613  				`{"45":{"workload":1}}`,
   614  				`{"46":{"workload":1}}`,
   615  				``,
   616  				``,
   617  				``,
   618  				``,
   619  			},
   620  			expected: GlobalReactorState{
   621  				Owner:    map[string]struct{}{"22317526c4fc9a38": {}},
   622  				Captures: map[CaptureID]*CaptureInfo{},
   623  				Changefeeds: map[ChangeFeedID]*ChangefeedReactorState{
   624  					"test2": {
   625  						ID:            "test2",
   626  						TaskStatuses:  map[string]*TaskStatus{},
   627  						TaskPositions: map[CaptureID]*TaskPosition{},
   628  						Workloads: map[CaptureID]TaskWorkload{
   629  							"55551111": {46: {Workload: 1}},
   630  						},
   631  					},
   632  				},
   633  			},
   634  		},
   635  	}
   636  	for _, tc := range testCases {
   637  		state := NewGlobalState()
   638  		for i, k := range tc.updateKey {
   639  			value := []byte(tc.updateValue[i])
   640  			if len(value) == 0 {
   641  				value = nil
   642  			}
   643  			err := state.Update(util.NewEtcdKey(k), value, false)
   644  			c.Assert(err, check.IsNil)
   645  		}
   646  		c.Assert(cmp.Equal(state, &tc.expected, cmpopts.IgnoreUnexported(GlobalReactorState{}, ChangefeedReactorState{})), check.IsTrue,
   647  			check.Commentf("%s", cmp.Diff(state, &tc.expected, cmpopts.IgnoreUnexported(GlobalReactorState{}, ChangefeedReactorState{}))))
   648  	}
   649  }
   650  
   651  func (s *stateSuite) TestCheckChangefeedNormal(c *check.C) {
   652  	defer testleak.AfterTest(c)()
   653  	state := NewChangefeedReactorState("test1")
   654  	stateTester := orchestrator.NewReactorStateTester(c, state, nil)
   655  	state.CheckChangefeedNormal()
   656  	stateTester.MustApplyPatches()
   657  	state.PatchInfo(func(info *ChangeFeedInfo) (*ChangeFeedInfo, bool, error) {
   658  		return &ChangeFeedInfo{SinkURI: "123", AdminJobType: AdminNone, Config: &config.ReplicaConfig{}}, true, nil
   659  	})
   660  	state.PatchStatus(func(status *ChangeFeedStatus) (*ChangeFeedStatus, bool, error) {
   661  		return &ChangeFeedStatus{ResolvedTs: 1, AdminJobType: AdminNone}, true, nil
   662  	})
   663  	state.CheckChangefeedNormal()
   664  	stateTester.MustApplyPatches()
   665  	c.Assert(state.Status.ResolvedTs, check.Equals, uint64(1))
   666  
   667  	state.PatchInfo(func(info *ChangeFeedInfo) (*ChangeFeedInfo, bool, error) {
   668  		info.AdminJobType = AdminStop
   669  		return info, true, nil
   670  	})
   671  	state.PatchStatus(func(status *ChangeFeedStatus) (*ChangeFeedStatus, bool, error) {
   672  		status.ResolvedTs = 2
   673  		return status, true, nil
   674  	})
   675  	state.CheckChangefeedNormal()
   676  	stateTester.MustApplyPatches()
   677  	c.Assert(state.Status.ResolvedTs, check.Equals, uint64(1))
   678  
   679  	state.PatchStatus(func(status *ChangeFeedStatus) (*ChangeFeedStatus, bool, error) {
   680  		status.ResolvedTs = 2
   681  		return status, true, nil
   682  	})
   683  	state.CheckChangefeedNormal()
   684  	stateTester.MustApplyPatches()
   685  	c.Assert(state.Status.ResolvedTs, check.Equals, uint64(2))
   686  }