github.com/pingcap/tiflow@v0.0.0-20240520035814-5bf52d54e205/cdc/processor/manager_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 processor 15 16 import ( 17 "bytes" 18 "context" 19 "fmt" 20 "math" 21 "testing" 22 "time" 23 24 "github.com/pingcap/tiflow/cdc/model" 25 "github.com/pingcap/tiflow/cdc/vars" 26 "github.com/pingcap/tiflow/pkg/config" 27 "github.com/pingcap/tiflow/pkg/etcd" 28 "github.com/pingcap/tiflow/pkg/orchestrator" 29 "github.com/pingcap/tiflow/pkg/upstream" 30 "github.com/stretchr/testify/require" 31 ) 32 33 type managerTester struct { 34 manager *managerImpl 35 state *orchestrator.GlobalReactorState 36 tester *orchestrator.ReactorStateTester 37 //nolint:unused 38 liveness model.Liveness 39 } 40 41 // NewManager4Test creates a new processor manager for test 42 func NewManager4Test( 43 t *testing.T, 44 liveness *model.Liveness, 45 globalVars *vars.GlobalVars, 46 ) *managerImpl { 47 captureInfo := &model.CaptureInfo{ID: "capture-test", AdvertiseAddr: "127.0.0.1:0000"} 48 cfg := config.NewDefaultSchedulerConfig() 49 m := NewManager(captureInfo, upstream.NewManager4Test(nil), liveness, cfg, globalVars).(*managerImpl) 50 m.newProcessor = func( 51 info *model.ChangeFeedInfo, 52 status *model.ChangeFeedStatus, 53 captureInfo *model.CaptureInfo, 54 changefeedID model.ChangeFeedID, 55 up *upstream.Upstream, 56 liveness *model.Liveness, 57 changefeedEpoch uint64, 58 cfg *config.SchedulerConfig, 59 client etcd.OwnerCaptureInfoClient, 60 globalVars *vars.GlobalVars, 61 ) *processor { 62 return newProcessor4Test(t, info, status, captureInfo, m.liveness, cfg, false, client, globalVars) 63 } 64 return m 65 } 66 67 //nolint:unused 68 func (s *managerTester) resetSuit(globalVars *vars.GlobalVars, 69 t *testing.T, 70 ) { 71 s.manager = NewManager4Test(t, &s.liveness, globalVars) 72 s.state = orchestrator.NewGlobalStateForTest(etcd.DefaultCDCClusterID) 73 captureInfoBytes, err := globalVars.CaptureInfo.Marshal() 74 require.Nil(t, err) 75 s.tester = orchestrator.NewReactorStateTester(t, s.state, map[string]string{ 76 fmt.Sprintf("%s/capture/%s", 77 etcd.DefaultClusterAndMetaPrefix, 78 globalVars.CaptureInfo.ID): string(captureInfoBytes), 79 }) 80 } 81 82 func TestChangefeed(t *testing.T) { 83 globalVars := vars.NewGlobalVars4Test() 84 ctx := context.Background() 85 s := &managerTester{} 86 s.resetSuit(globalVars, t) 87 var err error 88 89 // no changefeed 90 _, err = s.manager.Tick(ctx, s.state) 91 require.Nil(t, err) 92 93 changefeedID := model.DefaultChangeFeedID("test-changefeed") 94 // an inactive changefeed 95 s.state.Changefeeds[changefeedID] = orchestrator.NewChangefeedReactorState( 96 etcd.DefaultCDCClusterID, changefeedID) 97 _, err = s.manager.Tick(ctx, s.state) 98 s.tester.MustApplyPatches() 99 require.Nil(t, err) 100 require.Len(t, s.manager.processors, 0) 101 102 // an active changefeed 103 s.state.Changefeeds[changefeedID].PatchInfo( 104 func(info *model.ChangeFeedInfo) (*model.ChangeFeedInfo, bool, error) { 105 return &model.ChangeFeedInfo{ 106 SinkURI: "blackhole://", 107 CreateTime: time.Now(), 108 StartTs: 0, 109 TargetTs: math.MaxUint64, 110 Config: config.GetDefaultReplicaConfig(), 111 }, true, nil 112 }) 113 s.state.Changefeeds[changefeedID].PatchStatus( 114 func(status *model.ChangeFeedStatus) (*model.ChangeFeedStatus, bool, error) { 115 return &model.ChangeFeedStatus{}, true, nil 116 }) 117 s.tester.MustApplyPatches() 118 _, err = s.manager.Tick(ctx, s.state) 119 s.tester.MustApplyPatches() 120 require.Nil(t, err) 121 require.Len(t, s.manager.processors, 1) 122 123 // processor return errors 124 s.state.Changefeeds[changefeedID].PatchStatus( 125 func(status *model.ChangeFeedStatus) (*model.ChangeFeedStatus, bool, error) { 126 status.AdminJobType = model.AdminStop 127 return status, true, nil 128 }) 129 s.tester.MustApplyPatches() 130 _, err = s.manager.Tick(ctx, s.state) 131 s.tester.MustApplyPatches() 132 require.Nil(t, err) 133 require.Len(t, s.manager.processors, 0) 134 } 135 136 func TestDebugInfo(t *testing.T) { 137 globalVars := vars.NewGlobalVars4Test() 138 ctx := context.Background() 139 s := &managerTester{} 140 s.resetSuit(globalVars, t) 141 var err error 142 143 // no changefeed 144 _, err = s.manager.Tick(ctx, s.state) 145 require.Nil(t, err) 146 147 changefeedID := model.DefaultChangeFeedID("test-changefeed") 148 // an active changefeed 149 s.state.Changefeeds[changefeedID] = orchestrator.NewChangefeedReactorState( 150 etcd.DefaultCDCClusterID, changefeedID) 151 s.state.Changefeeds[changefeedID].PatchInfo( 152 func(info *model.ChangeFeedInfo) (*model.ChangeFeedInfo, bool, error) { 153 return &model.ChangeFeedInfo{ 154 SinkURI: "blackhole://", 155 CreateTime: time.Now(), 156 StartTs: 1, 157 TargetTs: math.MaxUint64, 158 Config: config.GetDefaultReplicaConfig(), 159 }, true, nil 160 }) 161 s.state.Changefeeds[changefeedID].PatchStatus( 162 func(status *model.ChangeFeedStatus) (*model.ChangeFeedStatus, bool, error) { 163 return &model.ChangeFeedStatus{}, true, nil 164 }) 165 s.tester.MustApplyPatches() 166 _, err = s.manager.Tick(ctx, s.state) 167 require.Nil(t, err) 168 s.tester.MustApplyPatches() 169 require.Len(t, s.manager.processors, 1) 170 171 // Do a no operation tick to lazy init the processor. 172 _, err = s.manager.Tick(ctx, s.state) 173 require.Nil(t, err) 174 s.tester.MustApplyPatches() 175 176 stdCtx, cancel := context.WithCancel(context.Background()) 177 done := make(chan struct{}) 178 go func() { 179 defer close(done) 180 for { 181 select { 182 case <-stdCtx.Done(): 183 return 184 default: 185 } 186 _, err = s.manager.Tick(ctx, s.state) 187 require.Nil(t, err) 188 s.tester.MustApplyPatches() 189 } 190 }() 191 doneM := make(chan error, 1) 192 buf := bytes.NewBufferString("") 193 s.manager.WriteDebugInfo(ctx, buf, doneM) 194 <-doneM 195 require.Greater(t, len(buf.String()), 0) 196 197 // Stop tick so that we can close manager safely. 198 cancel() 199 <-done 200 s.manager.Close() 201 } 202 203 func TestClose(t *testing.T) { 204 globalVars := vars.NewGlobalVars4Test() 205 ctx := context.Background() 206 s := &managerTester{} 207 s.resetSuit(globalVars, t) 208 var err error 209 210 // no changefeed 211 _, err = s.manager.Tick(ctx, s.state) 212 require.Nil(t, err) 213 214 changefeedID := model.DefaultChangeFeedID("test-changefeed") 215 // an active changefeed 216 s.state.Changefeeds[changefeedID] = orchestrator.NewChangefeedReactorState( 217 etcd.DefaultCDCClusterID, changefeedID) 218 s.state.Changefeeds[changefeedID].PatchInfo( 219 func(info *model.ChangeFeedInfo) (*model.ChangeFeedInfo, bool, error) { 220 return &model.ChangeFeedInfo{ 221 SinkURI: "blackhole://", 222 CreateTime: time.Now(), 223 StartTs: 0, 224 TargetTs: math.MaxUint64, 225 Config: config.GetDefaultReplicaConfig(), 226 }, true, nil 227 }) 228 s.state.Changefeeds[changefeedID].PatchStatus( 229 func(status *model.ChangeFeedStatus) (*model.ChangeFeedStatus, bool, error) { 230 return &model.ChangeFeedStatus{}, true, nil 231 }) 232 s.tester.MustApplyPatches() 233 _, err = s.manager.Tick(ctx, s.state) 234 require.Nil(t, err) 235 s.tester.MustApplyPatches() 236 require.Len(t, s.manager.processors, 1) 237 238 s.manager.Close() 239 require.Len(t, s.manager.processors, 0) 240 } 241 242 func TestSendCommandError(t *testing.T) { 243 globalVars := vars.NewGlobalVars4Test() 244 liveness := model.LivenessCaptureAlive 245 cfg := config.NewDefaultSchedulerConfig() 246 m := NewManager(&model.CaptureInfo{ID: "capture-test"}, nil, &liveness, cfg, globalVars).(*managerImpl) 247 ctx, cancel := context.WithCancel(context.TODO()) 248 cancel() 249 // Use unbuffered channel to stable test. 250 m.commandQueue = make(chan *command) 251 done := make(chan error, 1) 252 err := m.sendCommand(ctx, commandTpWriteDebugInfo, nil, done) 253 require.Error(t, err) 254 select { 255 case <-done: 256 case <-time.After(time.Second): 257 require.FailNow(t, "done must be closed") 258 } 259 } 260 261 func TestManagerLiveness(t *testing.T) { 262 globalVars := vars.NewGlobalVars4Test() 263 ctx := context.Background() 264 s := &managerTester{} 265 s.resetSuit(globalVars, t) 266 var err error 267 268 changefeedID := model.DefaultChangeFeedID("test-changefeed") 269 270 // no changefeed 271 _, err = s.manager.Tick(ctx, s.state) 272 require.Nil(t, err) 273 // an inactive changefeed 274 s.state.Changefeeds[changefeedID] = orchestrator.NewChangefeedReactorState( 275 etcd.DefaultCDCClusterID, changefeedID) 276 _, err = s.manager.Tick(ctx, s.state) 277 s.tester.MustApplyPatches() 278 require.Nil(t, err) 279 require.Len(t, s.manager.processors, 0) 280 // an active changefeed 281 s.state.Changefeeds[changefeedID].PatchInfo( 282 func(info *model.ChangeFeedInfo) (*model.ChangeFeedInfo, bool, error) { 283 return &model.ChangeFeedInfo{ 284 SinkURI: "blackhole://", 285 CreateTime: time.Now(), 286 StartTs: 0, 287 TargetTs: math.MaxUint64, 288 Config: config.GetDefaultReplicaConfig(), 289 }, true, nil 290 }) 291 s.state.Changefeeds[changefeedID].PatchStatus( 292 func(status *model.ChangeFeedStatus) (*model.ChangeFeedStatus, bool, error) { 293 return &model.ChangeFeedStatus{}, true, nil 294 }) 295 s.tester.MustApplyPatches() 296 _, err = s.manager.Tick(ctx, s.state) 297 s.tester.MustApplyPatches() 298 require.Nil(t, err) 299 require.Len(t, s.manager.processors, 1) 300 301 p := s.manager.processors[changefeedID] 302 require.Equal(t, model.LivenessCaptureAlive, p.liveness.Load()) 303 s.liveness.Store(model.LivenessCaptureStopping) 304 require.Equal(t, model.LivenessCaptureStopping, p.liveness.Load()) 305 }