github.com/pingcap/tiflow@v0.0.0-20240520035814-5bf52d54e205/cdc/api/v1/api_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 v1 15 16 import ( 17 "bytes" 18 "context" 19 "encoding/json" 20 "fmt" 21 "net/http" 22 "net/http/httptest" 23 "testing" 24 25 "github.com/gin-gonic/gin" 26 "github.com/golang/mock/gomock" 27 "github.com/pingcap/log" 28 "github.com/pingcap/tiflow/cdc/capture" 29 mock_capture "github.com/pingcap/tiflow/cdc/capture/mock" 30 mock2 "github.com/pingcap/tiflow/cdc/controller/mock" 31 "github.com/pingcap/tiflow/cdc/model" 32 "github.com/pingcap/tiflow/cdc/owner" 33 mock_owner "github.com/pingcap/tiflow/cdc/owner/mock" 34 "github.com/pingcap/tiflow/cdc/scheduler" 35 cerror "github.com/pingcap/tiflow/pkg/errors" 36 mock_etcd "github.com/pingcap/tiflow/pkg/etcd/mock" 37 "github.com/stretchr/testify/mock" 38 "github.com/stretchr/testify/require" 39 "go.uber.org/zap" 40 ) 41 42 var ( 43 changeFeedID = model.DefaultChangeFeedID("test-changeFeed") 44 captureID = "test-capture" 45 nonExistChangefeedID = model.DefaultChangeFeedID("non-exist-changefeed") 46 ) 47 48 type mockStatusProvider struct { 49 mock.Mock 50 } 51 52 type testCase struct { 53 url string 54 method string 55 } 56 57 func (p *mockStatusProvider) GetAllChangeFeedStatuses(ctx context.Context) ( 58 map[model.ChangeFeedID]*model.ChangeFeedStatusForAPI, error, 59 ) { 60 args := p.Called(ctx) 61 return args.Get(0).(map[model.ChangeFeedID]*model.ChangeFeedStatusForAPI), args.Error(1) 62 } 63 64 func (p *mockStatusProvider) GetChangeFeedStatus(ctx context.Context, changefeedID model.ChangeFeedID) ( 65 *model.ChangeFeedStatusForAPI, error, 66 ) { 67 args := p.Called(ctx, changefeedID) 68 log.Info("err", zap.Error(args.Error(1))) 69 return args.Get(0).(*model.ChangeFeedStatusForAPI), args.Error(1) 70 } 71 72 func (p *mockStatusProvider) GetAllChangeFeedInfo(ctx context.Context) ( 73 map[model.ChangeFeedID]*model.ChangeFeedInfo, error, 74 ) { 75 args := p.Called(ctx) 76 return args.Get(0).(map[model.ChangeFeedID]*model.ChangeFeedInfo), args.Error(1) 77 } 78 79 func (p *mockStatusProvider) GetChangeFeedInfo(ctx context.Context, changefeedID model.ChangeFeedID) ( 80 *model.ChangeFeedInfo, error, 81 ) { 82 args := p.Called(ctx) 83 return args.Get(0).(*model.ChangeFeedInfo), args.Error(1) 84 } 85 86 func (p *mockStatusProvider) GetAllTaskStatuses(ctx context.Context, changefeedID model.ChangeFeedID) ( 87 map[model.CaptureID]*model.TaskStatus, error, 88 ) { 89 args := p.Called(ctx) 90 return args.Get(0).(map[model.CaptureID]*model.TaskStatus), args.Error(1) 91 } 92 93 func (p *mockStatusProvider) GetProcessors(ctx context.Context) ([]*model.ProcInfoSnap, error) { 94 args := p.Called(ctx) 95 return args.Get(0).([]*model.ProcInfoSnap), args.Error(1) 96 } 97 98 func (p *mockStatusProvider) GetCaptures(ctx context.Context) ([]*model.CaptureInfo, error) { 99 args := p.Called(ctx) 100 return args.Get(0).([]*model.CaptureInfo), args.Error(1) 101 } 102 103 func (p *mockStatusProvider) GetChangeFeedSyncedStatus(ctx context.Context, 104 changefeedID model.ChangeFeedID, 105 ) (*model.ChangeFeedSyncedStatusForAPI, error) { 106 args := p.Called(ctx) 107 return args.Get(0).(*model.ChangeFeedSyncedStatusForAPI), args.Error(1) 108 } 109 110 func (p *mockStatusProvider) IsHealthy(ctx context.Context) (bool, error) { 111 args := p.Called(ctx) 112 return args.Get(0).(bool), args.Error(1) 113 } 114 115 func (p *mockStatusProvider) IsChangefeedOwner(ctx context.Context, id model.ChangeFeedID) (bool, error) { 116 args := p.Called(ctx, id) 117 return args.Get(0).(bool), args.Error(1) 118 } 119 120 func newRouter(c capture.Capture, p owner.StatusProvider) *gin.Engine { 121 router := gin.New() 122 RegisterOpenAPIRoutes(router, NewOpenAPI4Test(c, p)) 123 return router 124 } 125 126 func newRouterWithoutStatusProvider(c capture.Capture) *gin.Engine { 127 router := gin.New() 128 RegisterOpenAPIRoutes(router, NewOpenAPI(c)) 129 return router 130 } 131 132 func newStatusProvider() *mockStatusProvider { 133 statusProvider := &mockStatusProvider{} 134 statusProvider.On("GetChangeFeedStatus", mock.Anything, changeFeedID). 135 Return(&model.ChangeFeedStatusForAPI{CheckpointTs: 1}, nil) 136 137 statusProvider.On("GetChangeFeedStatus", mock.Anything, nonExistChangefeedID). 138 Return(new(model.ChangeFeedStatusForAPI), 139 cerror.ErrChangeFeedNotExists.GenWithStackByArgs(nonExistChangefeedID)) 140 141 statusProvider.On("GetAllTaskStatuses", mock.Anything). 142 Return(map[model.CaptureID]*model.TaskStatus{captureID: {}}, nil) 143 144 statusProvider.On("GetAllChangeFeedStatuses", mock.Anything). 145 Return(map[model.ChangeFeedID]*model.ChangeFeedStatusForAPI{ 146 model.ChangeFeedID4Test("ab", "123"): {CheckpointTs: 1}, 147 model.ChangeFeedID4Test("ab", "13"): {CheckpointTs: 2}, 148 model.ChangeFeedID4Test("abc", "123"): {CheckpointTs: 1}, 149 model.ChangeFeedID4Test("def", "456"): {CheckpointTs: 2}, 150 }, nil) 151 152 statusProvider.On("GetAllChangeFeedInfo", mock.Anything). 153 Return(map[model.ChangeFeedID]*model.ChangeFeedInfo{ 154 model.ChangeFeedID4Test("ab", "123"): {State: model.StateNormal}, 155 model.ChangeFeedID4Test("ab", "13"): {State: model.StateStopped}, 156 model.ChangeFeedID4Test("abc", "123"): {State: model.StateNormal}, 157 model.ChangeFeedID4Test("def", "456"): {State: model.StateStopped}, 158 }, nil) 159 160 statusProvider.On("GetAllTaskStatuses", mock.Anything). 161 Return(map[model.CaptureID]*model.TaskStatus{captureID: {}}, nil) 162 163 statusProvider.On("GetChangeFeedInfo", mock.Anything). 164 Return(&model.ChangeFeedInfo{ 165 State: model.StateNormal, 166 CreatorVersion: "v6.5.1", 167 }, nil) 168 169 statusProvider.On("GetProcessors", mock.Anything). 170 Return([]*model.ProcInfoSnap{{CfID: changeFeedID, CaptureID: captureID}}, nil) 171 172 statusProvider.On("GetCaptures", mock.Anything). 173 Return([]*model.CaptureInfo{{ID: captureID}}, nil) 174 175 return statusProvider 176 } 177 178 func TestListChangefeed(t *testing.T) { 179 t.Parallel() 180 ctrl := gomock.NewController(t) 181 mo := mock2.NewMockController(ctrl) 182 cp := capture.NewCaptureWithController4Test(mock_owner.NewMockOwner(ctrl), mo) 183 mo.EXPECT().GetAllChangeFeedCheckpointTs(gomock.Any()).Return( 184 map[model.ChangeFeedID]uint64{ 185 model.ChangeFeedID4Test("ab", "123"): 1, 186 model.ChangeFeedID4Test("ab", "13"): 2, 187 model.ChangeFeedID4Test("abc", "123"): 1, 188 model.ChangeFeedID4Test("def", "456"): 2, 189 }, nil).AnyTimes() 190 mo.EXPECT().GetAllChangeFeedInfo(gomock.Any()).Return( 191 map[model.ChangeFeedID]*model.ChangeFeedInfo{ 192 model.ChangeFeedID4Test("ab", "123"): {State: model.StateNormal}, 193 model.ChangeFeedID4Test("ab", "13"): {State: model.StateStopped}, 194 model.ChangeFeedID4Test("abc", "123"): {State: model.StateNormal}, 195 model.ChangeFeedID4Test("def", "456"): {State: model.StateStopped}, 196 }, nil).AnyTimes() 197 router := newRouter(cp, newStatusProvider()) 198 199 // test list changefeed succeeded 200 api := testCase{url: "/api/v1/changefeeds", method: "GET"} 201 w := httptest.NewRecorder() 202 req, _ := http.NewRequestWithContext(context.Background(), api.method, api.url, nil) 203 router.ServeHTTP(w, req) 204 require.Equal(t, 200, w.Code) 205 var resp []model.ChangefeedCommonInfo 206 fmt.Println(w.Body.String()) 207 err := json.NewDecoder(w.Body).Decode(&resp) 208 require.Nil(t, err) 209 require.Equal(t, 4, len(resp)) 210 require.Equal(t, "ab", resp[0].Namespace) 211 require.Equal(t, "123", resp[0].ID) 212 require.Equal(t, "ab", resp[1].Namespace) 213 require.Equal(t, "13", resp[1].ID) 214 require.Equal(t, "abc", resp[2].Namespace) 215 require.Equal(t, "123", resp[2].ID) 216 require.Equal(t, "def", resp[3].Namespace) 217 require.Equal(t, "456", resp[3].ID) 218 219 // test list changefeed with specific state 220 api = testCase{url: fmt.Sprintf("/api/v1/changefeeds?state=%s", "stopped"), method: "GET"} 221 w = httptest.NewRecorder() 222 req, _ = http.NewRequestWithContext(context.Background(), api.method, api.url, nil) 223 router.ServeHTTP(w, req) 224 require.Equal(t, 200, w.Code) 225 resp = []model.ChangefeedCommonInfo{} 226 err = json.NewDecoder(w.Body).Decode(&resp) 227 require.Nil(t, err) 228 require.Equal(t, 2, len(resp)) 229 require.Equal(t, model.StateStopped, resp[0].FeedState) 230 require.Equal(t, model.StateStopped, resp[1].FeedState) 231 require.Equal(t, uint64(0x2), resp[0].CheckpointTSO) 232 require.Equal(t, uint64(0x2), resp[1].CheckpointTSO) 233 } 234 235 func TestGetChangefeed(t *testing.T) { 236 t.Parallel() 237 ctrl := gomock.NewController(t) 238 mo := mock_owner.NewMockOwner(ctrl) 239 etcdClient := mock_etcd.NewMockCDCEtcdClient(ctrl) 240 etcdClient.EXPECT().GetClusterID().Return("abcd").AnyTimes() 241 cp := capture.NewCaptureWithController4Test(mo, mock2.NewMockController(ctrl)) 242 cp.EtcdClient = etcdClient 243 controller := mock2.NewMockController(ctrl) 244 capture := mock_capture.NewMockCapture(ctrl) 245 capture.EXPECT().GetController().Return(controller, nil).AnyTimes() 246 capture.EXPECT().GetOwner().Return(mo, nil).AnyTimes() 247 capture.EXPECT().IsReady().Return(true).AnyTimes() 248 capture.EXPECT().IsController().Return(true).AnyTimes() 249 statusProvider := newStatusProvider() 250 capture.EXPECT().StatusProvider().Return(statusProvider).AnyTimes() 251 router := newRouter(capture, statusProvider) 252 statusProvider.On("IsChangefeedOwner", mock.Anything, mock.Anything). 253 Return(true, nil).Times(3) 254 255 // test get changefeed succeeded 256 api := testCase{url: fmt.Sprintf("/api/v1/changefeeds/%s", changeFeedID.ID), method: "GET"} 257 w := httptest.NewRecorder() 258 req, _ := http.NewRequestWithContext(context.Background(), api.method, api.url, nil) 259 router.ServeHTTP(w, req) 260 require.Equal(t, 200, w.Code) 261 var resp model.ChangefeedDetail 262 err := json.NewDecoder(w.Body).Decode(&resp) 263 require.Nil(t, err) 264 require.Equal(t, model.StateNormal, resp.FeedState) 265 require.Equal(t, "v6.5.1", resp.CreatorVersion) 266 267 // test get changefeed failed 268 api = testCase{ 269 url: fmt.Sprintf("/api/v1/changefeeds/%s", nonExistChangefeedID.ID), 270 method: "GET", 271 } 272 w = httptest.NewRecorder() 273 req, _ = http.NewRequestWithContext(context.Background(), api.method, api.url, nil) 274 router.ServeHTTP(w, req) 275 require.Equal(t, 400, w.Code) 276 respErr := model.HTTPError{} 277 err = json.NewDecoder(w.Body).Decode(&respErr) 278 require.Nil(t, err) 279 require.Contains(t, respErr.Error, "changefeed not exists") 280 } 281 282 func TestPauseChangefeed(t *testing.T) { 283 t.Parallel() 284 ctrl := gomock.NewController(t) 285 mo := mock_owner.NewMockOwner(ctrl) 286 controller := mock2.NewMockController(ctrl) 287 capture := mock_capture.NewMockCapture(ctrl) 288 capture.EXPECT().GetController().Return(controller, nil).AnyTimes() 289 capture.EXPECT().GetOwner().Return(mo, nil).AnyTimes() 290 capture.EXPECT().IsReady().Return(true).AnyTimes() 291 capture.EXPECT().IsController().Return(true).AnyTimes() 292 statusProvider := newStatusProvider() 293 capture.EXPECT().StatusProvider().Return(statusProvider).AnyTimes() 294 router := newRouter(capture, statusProvider) 295 statusProvider.On("IsChangefeedOwner", mock.Anything, changeFeedID). 296 Return(true, nil).Twice() 297 statusProvider.On("IsChangefeedOwner", mock.Anything, nonExistChangefeedID). 298 Return(true, nil).Once() 299 300 // test pause changefeed succeeded 301 mo.EXPECT(). 302 EnqueueJob(gomock.Any(), gomock.Any()). 303 Do(func(adminJob model.AdminJob, done chan<- error) { 304 require.EqualValues(t, changeFeedID, adminJob.CfID) 305 require.EqualValues(t, model.AdminStop, adminJob.Type) 306 close(done) 307 }) 308 api := testCase{ 309 url: fmt.Sprintf("/api/v1/changefeeds/%s/pause", changeFeedID.ID), 310 method: "POST", 311 } 312 w := httptest.NewRecorder() 313 req, _ := http.NewRequestWithContext(context.Background(), api.method, api.url, nil) 314 router.ServeHTTP(w, req) 315 require.Equal(t, 202, w.Code) 316 317 // test pause changefeed failed from owner side 318 mo.EXPECT(). 319 EnqueueJob(gomock.Any(), gomock.Any()). 320 Do(func(adminJob model.AdminJob, done chan<- error) { 321 done <- cerror.ErrChangeFeedNotExists.FastGenByArgs(adminJob.CfID) 322 close(done) 323 }) 324 api = testCase{ 325 url: fmt.Sprintf("/api/v1/changefeeds/%s/pause", changeFeedID.ID), 326 method: "POST", 327 } 328 w = httptest.NewRecorder() 329 req, _ = http.NewRequestWithContext(context.Background(), api.method, api.url, nil) 330 router.ServeHTTP(w, req) 331 require.Equal(t, 400, w.Code) 332 respErr := model.HTTPError{} 333 err := json.NewDecoder(w.Body).Decode(&respErr) 334 require.Nil(t, err) 335 require.Contains(t, respErr.Error, "changefeed not exists") 336 337 // test pause changefeed failed 338 api = testCase{ 339 url: fmt.Sprintf("/api/v1/changefeeds/%s/pause", nonExistChangefeedID.ID), 340 method: "POST", 341 } 342 w = httptest.NewRecorder() 343 req, _ = http.NewRequestWithContext(context.Background(), api.method, api.url, nil) 344 router.ServeHTTP(w, req) 345 require.Equal(t, 400, w.Code) 346 respErr = model.HTTPError{} 347 err = json.NewDecoder(w.Body).Decode(&respErr) 348 require.Nil(t, err) 349 require.Contains(t, respErr.Error, "changefeed not exists") 350 } 351 352 func TestResumeChangefeed(t *testing.T) { 353 t.Parallel() 354 ctrl := gomock.NewController(t) 355 mo := mock_owner.NewMockOwner(ctrl) 356 controller := mock2.NewMockController(ctrl) 357 cp := mock_capture.NewMockCapture(ctrl) 358 cp.EXPECT().GetController().Return(controller, nil).AnyTimes() 359 cp.EXPECT().GetOwner().Return(mo, nil).AnyTimes() 360 cp.EXPECT().IsReady().Return(true).AnyTimes() 361 statusProvider := mock_owner.NewMockStatusProvider(ctrl) 362 statusProvider.EXPECT().IsChangefeedOwner(gomock.Any(), gomock.Any()). 363 Return(true, nil).AnyTimes() 364 cp.EXPECT().StatusProvider().Return(statusProvider).AnyTimes() 365 dataProvider := newStatusProvider() 366 statusProvider.EXPECT().GetChangeFeedStatus(gomock.Any(), changeFeedID).Return( 367 dataProvider.GetChangeFeedStatus(context.Background(), changeFeedID)) 368 router := newRouter(cp, statusProvider) 369 370 // test resume changefeed succeeded 371 mo.EXPECT(). 372 EnqueueJob(gomock.Any(), gomock.Any()). 373 Do(func(adminJob model.AdminJob, done chan<- error) { 374 require.EqualValues(t, changeFeedID, adminJob.CfID) 375 require.EqualValues(t, model.AdminResume, adminJob.Type) 376 close(done) 377 }) 378 api := testCase{ 379 url: fmt.Sprintf("/api/v1/changefeeds/%s/resume", changeFeedID.ID), 380 method: "POST", 381 } 382 w := httptest.NewRecorder() 383 req, _ := http.NewRequestWithContext(context.Background(), api.method, api.url, nil) 384 router.ServeHTTP(w, req) 385 require.Equal(t, 202, w.Code) 386 387 // test resume changefeed failed from owner side. 388 statusProvider.EXPECT().GetChangeFeedStatus(gomock.Any(), changeFeedID).Return( 389 dataProvider.GetChangeFeedStatus(context.Background(), changeFeedID)) 390 mo.EXPECT(). 391 EnqueueJob(gomock.Any(), gomock.Any()). 392 Do(func(adminJob model.AdminJob, done chan<- error) { 393 done <- cerror.ErrChangeFeedNotExists.FastGenByArgs(adminJob.CfID) 394 close(done) 395 }) 396 api = testCase{ 397 url: fmt.Sprintf("/api/v1/changefeeds/%s/resume", changeFeedID.ID), 398 method: "POST", 399 } 400 w = httptest.NewRecorder() 401 req, _ = http.NewRequestWithContext(context.Background(), api.method, api.url, nil) 402 router.ServeHTTP(w, req) 403 require.Equal(t, 400, w.Code) 404 respErr := model.HTTPError{} 405 err := json.NewDecoder(w.Body).Decode(&respErr) 406 require.Nil(t, err) 407 require.Contains(t, respErr.Error, "changefeed not exists") 408 409 // test resume changefeed failed 410 statusProvider.EXPECT().GetChangeFeedStatus(gomock.Any(), nonExistChangefeedID). 411 Return(nil, cerror.ErrChangeFeedNotExists) 412 api = testCase{ 413 url: fmt.Sprintf("/api/v1/changefeeds/%s/resume", nonExistChangefeedID.ID), 414 method: "POST", 415 } 416 w = httptest.NewRecorder() 417 req, _ = http.NewRequestWithContext(context.Background(), api.method, api.url, nil) 418 router.ServeHTTP(w, req) 419 require.Equal(t, 400, w.Code) 420 respErr = model.HTTPError{} 421 err = json.NewDecoder(w.Body).Decode(&respErr) 422 require.Nil(t, err) 423 require.Contains(t, respErr.Error, "changefeed not exists") 424 } 425 426 func TestRemoveChangefeed(t *testing.T) { 427 t.Parallel() 428 ctrl := gomock.NewController(t) 429 mo := mock_owner.NewMockOwner(ctrl) 430 431 controller := mock2.NewMockController(ctrl) 432 capture := mock_capture.NewMockCapture(ctrl) 433 capture.EXPECT().GetController().Return(controller, nil).AnyTimes() 434 capture.EXPECT().GetOwner().Return(mo, nil).AnyTimes() 435 capture.EXPECT().IsReady().Return(true).AnyTimes() 436 capture.EXPECT().IsController().Return(true).AnyTimes() 437 router1 := newRouterWithoutStatusProvider(capture) 438 439 // test remove changefeed succeeded 440 mo.EXPECT(). 441 EnqueueJob(gomock.Any(), gomock.Any()). 442 Do(func(adminJob model.AdminJob, done chan<- error) { 443 require.EqualValues(t, changeFeedID, adminJob.CfID) 444 require.EqualValues(t, model.AdminRemove, adminJob.Type) 445 close(done) 446 }) 447 controller.EXPECT().IsChangefeedExists(gomock.Any(), gomock.Any()).Return(true, nil) 448 controller.EXPECT().IsChangefeedExists(gomock.Any(), gomock.Any()).Return(false, nil) 449 api := testCase{url: fmt.Sprintf("/api/v1/changefeeds/%s", changeFeedID.ID), method: "DELETE"} 450 w := httptest.NewRecorder() 451 req, _ := http.NewRequestWithContext(context.Background(), api.method, api.url, nil) 452 router1.ServeHTTP(w, req) 453 require.Equal(t, 202, w.Code) 454 455 router2 := newRouterWithoutStatusProvider(capture) 456 controller.EXPECT().IsChangefeedExists(gomock.Any(), gomock.Any()).Return(true, nil) 457 458 // test remove changefeed failed from owner side 459 mo.EXPECT(). 460 EnqueueJob(gomock.Any(), gomock.Any()). 461 Do(func(adminJob model.AdminJob, done chan<- error) { 462 done <- cerror.ErrChangeFeedNotExists.FastGenByArgs(adminJob.CfID) 463 close(done) 464 }) 465 api = testCase{url: fmt.Sprintf("/api/v1/changefeeds/%s", changeFeedID.ID), method: "DELETE"} 466 w = httptest.NewRecorder() 467 req, _ = http.NewRequestWithContext(context.Background(), api.method, api.url, nil) 468 router2.ServeHTTP(w, req) 469 require.Equal(t, 400, w.Code) 470 respErr := model.HTTPError{} 471 err := json.NewDecoder(w.Body).Decode(&respErr) 472 require.Nil(t, err) 473 require.Contains(t, respErr.Error, "changefeed not exists") 474 475 // test remove changefeed failed 476 controller.EXPECT().IsChangefeedExists(gomock.Any(), nonExistChangefeedID).Return(false, nil) 477 api = testCase{ 478 url: fmt.Sprintf("/api/v1/changefeeds/%s", nonExistChangefeedID.ID), 479 method: "DELETE", 480 } 481 w = httptest.NewRecorder() 482 req, _ = http.NewRequestWithContext(context.Background(), api.method, api.url, nil) 483 router2.ServeHTTP(w, req) 484 require.Equal(t, 400, w.Code) 485 respErr = model.HTTPError{} 486 err = json.NewDecoder(w.Body).Decode(&respErr) 487 require.Nil(t, err) 488 require.Contains(t, respErr.Error, "changefeed not exists") 489 } 490 491 func TestRebalanceTables(t *testing.T) { 492 t.Parallel() 493 ctrl := gomock.NewController(t) 494 mo := mock_owner.NewMockOwner(ctrl) 495 controller := mock2.NewMockController(ctrl) 496 capture := mock_capture.NewMockCapture(ctrl) 497 capture.EXPECT().GetController().Return(controller, nil).AnyTimes() 498 capture.EXPECT().GetOwner().Return(mo, nil).AnyTimes() 499 capture.EXPECT().IsReady().Return(true).AnyTimes() 500 capture.EXPECT().IsController().Return(true).AnyTimes() 501 statusProvider := newStatusProvider() 502 capture.EXPECT().StatusProvider().Return(statusProvider).AnyTimes() 503 router := newRouter(capture, statusProvider) 504 statusProvider.On("IsChangefeedOwner", mock.Anything, mock.Anything). 505 Return(true, nil).Times(5) 506 507 // test rebalance table succeeded 508 mo.EXPECT(). 509 RebalanceTables(gomock.Any(), gomock.Any()). 510 Do(func(cfID model.ChangeFeedID, done chan<- error) { 511 require.EqualValues(t, cfID, changeFeedID) 512 close(done) 513 }) 514 api := testCase{ 515 url: fmt.Sprintf("/api/v1/changefeeds/%s/tables/rebalance_table", changeFeedID.ID), 516 method: "POST", 517 } 518 w := httptest.NewRecorder() 519 req, _ := http.NewRequestWithContext(context.Background(), api.method, api.url, nil) 520 router.ServeHTTP(w, req) 521 require.Equal(t, 202, w.Code) 522 523 // test rebalance table failed from owner side. 524 mo.EXPECT(). 525 RebalanceTables(gomock.Any(), gomock.Any()). 526 Do(func(cfID model.ChangeFeedID, done chan<- error) { 527 done <- cerror.ErrChangeFeedNotExists.FastGenByArgs(cfID) 528 close(done) 529 }) 530 api = testCase{ 531 url: fmt.Sprintf("/api/v1/changefeeds/%s/tables/rebalance_table", changeFeedID.ID), 532 method: "POST", 533 } 534 w = httptest.NewRecorder() 535 req, _ = http.NewRequestWithContext(context.Background(), api.method, api.url, nil) 536 router.ServeHTTP(w, req) 537 require.Equal(t, 400, w.Code) 538 respErr := model.HTTPError{} 539 err := json.NewDecoder(w.Body).Decode(&respErr) 540 require.Nil(t, err) 541 require.Contains(t, respErr.Error, "changefeed not exists") 542 543 // test rebalance table failed 544 api = testCase{ 545 url: fmt.Sprintf("/api/v1/changefeeds/%s/tables/rebalance_table", 546 nonExistChangefeedID.ID), 547 method: "POST", 548 } 549 w = httptest.NewRecorder() 550 req, _ = http.NewRequestWithContext(context.Background(), api.method, api.url, nil) 551 router.ServeHTTP(w, req) 552 require.Equal(t, 400, w.Code) 553 respErr = model.HTTPError{} 554 err = json.NewDecoder(w.Body).Decode(&respErr) 555 require.Nil(t, err) 556 require.Contains(t, respErr.Error, "changefeed not exists") 557 } 558 559 func TestDrainCapture(t *testing.T) { 560 t.Parallel() 561 562 statusProvider := newStatusProvider() 563 564 ctrl := gomock.NewController(t) 565 owner := mock_owner.NewMockOwner(ctrl) 566 controller := mock2.NewMockController(ctrl) 567 capture := capture.NewCaptureWithController4Test(owner, controller) 568 router := newRouter(capture, statusProvider) 569 570 captureInfo, err := capture.Info() 571 require.NoError(t, err) 572 data := model.DrainCaptureRequest{ 573 CaptureID: captureInfo.ID, 574 } 575 b, err := json.Marshal(&data) 576 require.NoError(t, err) 577 578 body := bytes.NewReader(b) 579 api := testCase{ 580 url: "/api/v1/captures/drain", 581 method: "PUT", 582 } 583 request, err := http.NewRequestWithContext(context.Background(), api.method, api.url, body) 584 require.NoError(t, err) 585 586 w := httptest.NewRecorder() 587 router.ServeHTTP(w, request) 588 // only has one capture 589 require.Equal(t, http.StatusBadRequest, w.Code) 590 respErr := model.HTTPError{} 591 err = json.NewDecoder(w.Body).Decode(&respErr) 592 require.Nil(t, err) 593 require.Contains(t, respErr.Code, "CDC:ErrSchedulerRequestFailed") 594 require.Contains(t, respErr.Error, "scheduler request failed") 595 596 statusProvider.ExpectedCalls = statusProvider. 597 ExpectedCalls[:len(statusProvider.ExpectedCalls)-1] 598 599 statusProvider.On("GetCaptures", mock.Anything). 600 Return([]*model.CaptureInfo{{ID: captureID}, {ID: captureInfo.ID}}, nil) 601 602 defer func() { 603 statusProvider.ExpectedCalls = statusProvider. 604 ExpectedCalls[:len(statusProvider.ExpectedCalls)-1] 605 606 statusProvider.On("GetCaptures", mock.Anything). 607 Return([]*model.CaptureInfo{{ID: captureID}}, nil) 608 }() 609 610 data = model.DrainCaptureRequest{CaptureID: "capture-not-found"} 611 b, err = json.Marshal(&data) 612 require.NoError(t, err) 613 body = bytes.NewReader(b) 614 615 request, err = http.NewRequestWithContext(context.Background(), api.method, api.url, body) 616 require.NoError(t, err) 617 618 w = httptest.NewRecorder() 619 router.ServeHTTP(w, request) 620 require.Equal(t, http.StatusBadRequest, w.Code) 621 respErr = model.HTTPError{} 622 err = json.NewDecoder(w.Body).Decode(&respErr) 623 require.NoError(t, err) 624 require.Contains(t, respErr.Code, "CDC:ErrCaptureNotExist") 625 require.Contains(t, respErr.Error, "capture not exists") 626 627 data = model.DrainCaptureRequest{CaptureID: "capture-for-test"} 628 b, err = json.Marshal(&data) 629 require.NoError(t, err) 630 body = bytes.NewReader(b) 631 request, err = http.NewRequestWithContext(context.Background(), api.method, api.url, body) 632 require.NoError(t, err) 633 634 w = httptest.NewRecorder() 635 router.ServeHTTP(w, request) 636 require.Equal(t, http.StatusBadRequest, w.Code) 637 respErr = model.HTTPError{} 638 err = json.NewDecoder(w.Body).Decode(&respErr) 639 require.NoError(t, err) 640 require.Contains(t, respErr.Code, "CDC:ErrSchedulerRequestFailed") 641 require.Contains(t, respErr.Error, "cannot drain the owner") 642 643 data = model.DrainCaptureRequest{CaptureID: captureID} 644 b, err = json.Marshal(&data) 645 require.NoError(t, err) 646 body = bytes.NewReader(b) 647 648 request, err = http.NewRequestWithContext(context.Background(), api.method, api.url, body) 649 require.NoError(t, err) 650 651 owner.EXPECT().DrainCapture(gomock.Any(), gomock.Any()). 652 Do(func(query *scheduler.Query, done chan<- error) { 653 query.Resp = &model.DrainCaptureResp{ 654 CurrentTableCount: 3, 655 } 656 done <- cerror.ErrSchedulerRequestFailed. 657 GenWithStack("not all captures initialized") 658 close(done) 659 }) 660 w = httptest.NewRecorder() 661 router.ServeHTTP(w, request) 662 require.Equal(t, http.StatusServiceUnavailable, w.Code) 663 respErr = model.HTTPError{} 664 err = json.NewDecoder(w.Body).Decode(&respErr) 665 require.NoError(t, err) 666 require.Contains(t, respErr.Code, "CDC:ErrSchedulerRequestFailed") 667 require.Contains(t, respErr.Error, "not all captures initialized") 668 669 body = bytes.NewReader(b) 670 request, err = http.NewRequestWithContext(context.Background(), api.method, api.url, body) 671 require.NoError(t, err) 672 673 owner.EXPECT().DrainCapture(gomock.Any(), gomock.Any()). 674 Do(func(query *scheduler.Query, done chan<- error) { 675 query.Resp = &model.DrainCaptureResp{ 676 CurrentTableCount: 3, 677 } 678 close(done) 679 }) 680 681 w = httptest.NewRecorder() 682 router.ServeHTTP(w, request) 683 require.Equal(t, http.StatusAccepted, w.Code) 684 685 var resp model.DrainCaptureResp 686 err = json.NewDecoder(w.Body).Decode(&resp) 687 require.NoError(t, err) 688 require.Equal(t, 3, resp.CurrentTableCount) 689 } 690 691 func TestMoveTable(t *testing.T) { 692 t.Parallel() 693 694 ctrl := gomock.NewController(t) 695 mo := mock_owner.NewMockOwner(ctrl) 696 controller := mock2.NewMockController(ctrl) 697 cp := mock_capture.NewMockCapture(ctrl) 698 cp.EXPECT().GetController().Return(controller, nil).AnyTimes() 699 cp.EXPECT().GetOwner().Return(mo, nil).AnyTimes() 700 cp.EXPECT().IsReady().Return(true).AnyTimes() 701 statusProvider := newStatusProvider() 702 cp.EXPECT().StatusProvider().Return(statusProvider).AnyTimes() 703 router := newRouter(cp, statusProvider) 704 statusProvider.On("IsChangefeedOwner", mock.Anything, mock.Anything). 705 Return(true, nil).Times(3) 706 // test move table succeeded 707 data := struct { 708 CaptureID string `json:"capture_id"` 709 TableID int64 `json:"table_id"` 710 }{captureID, 1} 711 b, err := json.Marshal(&data) 712 require.Nil(t, err) 713 body := bytes.NewReader(b) 714 mo.EXPECT(). 715 ScheduleTable(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()). 716 Do(func( 717 cfID model.ChangeFeedID, toCapture model.CaptureID, 718 tableID model.TableID, done chan<- error, 719 ) { 720 require.EqualValues(t, cfID, changeFeedID) 721 require.EqualValues(t, toCapture, data.CaptureID) 722 require.EqualValues(t, tableID, data.TableID) 723 close(done) 724 }) 725 api := testCase{ 726 url: fmt.Sprintf("/api/v1/changefeeds/%s/tables/move_table", changeFeedID.ID), 727 method: "POST", 728 } 729 w := httptest.NewRecorder() 730 req, _ := http.NewRequestWithContext(context.Background(), api.method, api.url, body) 731 router.ServeHTTP(w, req) 732 require.Equal(t, 202, w.Code) 733 734 // test move table failed from owner side. 735 data = struct { 736 CaptureID string `json:"capture_id"` 737 TableID int64 `json:"table_id"` 738 }{captureID, 1} 739 b, err = json.Marshal(&data) 740 require.Nil(t, err) 741 body = bytes.NewReader(b) 742 mo.EXPECT(). 743 ScheduleTable(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()). 744 Do(func( 745 cfID model.ChangeFeedID, toCapture model.CaptureID, 746 tableID model.TableID, done chan<- error, 747 ) { 748 require.EqualValues(t, cfID, changeFeedID) 749 require.EqualValues(t, toCapture, data.CaptureID) 750 require.EqualValues(t, tableID, data.TableID) 751 done <- cerror.ErrChangeFeedNotExists.FastGenByArgs(cfID) 752 close(done) 753 }) 754 api = testCase{ 755 url: fmt.Sprintf("/api/v1/changefeeds/%s/tables/move_table", changeFeedID.ID), 756 method: "POST", 757 } 758 w = httptest.NewRecorder() 759 req, _ = http.NewRequestWithContext(context.Background(), api.method, api.url, body) 760 router.ServeHTTP(w, req) 761 require.Equal(t, 400, w.Code) 762 respErr := model.HTTPError{} 763 err = json.NewDecoder(w.Body).Decode(&respErr) 764 require.Nil(t, err) 765 require.Contains(t, respErr.Error, "changefeed not exists") 766 767 // test move table failed 768 api = testCase{ 769 url: fmt.Sprintf("/api/v1/changefeeds/%s/tables/move_table", nonExistChangefeedID.ID), 770 method: "POST", 771 } 772 w = httptest.NewRecorder() 773 req, _ = http.NewRequestWithContext(context.Background(), api.method, api.url, body) 774 router.ServeHTTP(w, req) 775 require.Equal(t, 400, w.Code) 776 respErr = model.HTTPError{} 777 err = json.NewDecoder(w.Body).Decode(&respErr) 778 require.Nil(t, err) 779 require.Contains(t, respErr.Error, "changefeed not exists") 780 } 781 782 func TestResignOwner(t *testing.T) { 783 t.Parallel() 784 ctrl := gomock.NewController(t) 785 mo := mock2.NewMockController(ctrl) 786 cp := capture.NewCaptureWithController4Test(mock_owner.NewMockOwner(ctrl), mo) 787 router := newRouter(cp, newStatusProvider()) 788 // test resign owner succeeded 789 mo.EXPECT().AsyncStop() 790 api := testCase{url: "/api/v1/owner/resign", method: "POST"} 791 w := httptest.NewRecorder() 792 req, _ := http.NewRequestWithContext(context.Background(), api.method, api.url, nil) 793 router.ServeHTTP(w, req) 794 require.Equal(t, 202, w.Code) 795 } 796 797 func TestGetProcessor(t *testing.T) { 798 t.Parallel() 799 ctrl := gomock.NewController(t) 800 controller := mock2.NewMockController(ctrl) 801 cp := mock_capture.NewMockCapture(ctrl) 802 cp.EXPECT().GetController().Return(controller, nil).AnyTimes() 803 cp.EXPECT().IsReady().Return(true).AnyTimes() 804 statusProvider := mock_owner.NewMockStatusProvider(ctrl) 805 cp.EXPECT().StatusProvider().Return(statusProvider).AnyTimes() 806 router := newRouter(cp, statusProvider) 807 statusProvider.EXPECT().IsChangefeedOwner(gomock.Any(), gomock.Any()). 808 Return(true, nil).AnyTimes() 809 dataProvider := newStatusProvider() 810 statusProvider.EXPECT().GetChangeFeedInfo(gomock.Any(), changeFeedID).Return( 811 dataProvider.GetChangeFeedInfo(context.Background(), changeFeedID)).AnyTimes() 812 statusProvider.EXPECT().GetChangeFeedStatus(gomock.Any(), changeFeedID).Return( 813 dataProvider.GetChangeFeedStatus(context.Background(), changeFeedID)).AnyTimes() 814 statusProvider.EXPECT().GetProcessors(gomock.Any()).Return( 815 dataProvider.GetProcessors(context.Background())).AnyTimes() 816 statusProvider.EXPECT().GetAllTaskStatuses(gomock.Any(), gomock.Any()).Return( 817 dataProvider.GetAllTaskStatuses(context.Background(), changeFeedID)).AnyTimes() 818 // test get processor succeeded 819 api := testCase{ 820 url: fmt.Sprintf("/api/v1/processors/%s/%s", changeFeedID.ID, captureID), 821 method: "GET", 822 } 823 w := httptest.NewRecorder() 824 req, _ := http.NewRequestWithContext(context.Background(), api.method, api.url, nil) 825 router.ServeHTTP(w, req) 826 require.Equal(t, 200, w.Code) 827 828 // test get processor fail due to capture ID error 829 api = testCase{ 830 url: fmt.Sprintf("/api/v1/processors/%s/%s", changeFeedID.ID, "non-exist-capture"), 831 method: "GET", 832 } 833 w = httptest.NewRecorder() 834 req, _ = http.NewRequestWithContext(context.Background(), api.method, api.url, nil) 835 router.ServeHTTP(w, req) 836 require.Equal(t, 400, w.Code) 837 httpError := &model.HTTPError{} 838 err := json.NewDecoder(w.Body).Decode(httpError) 839 require.Nil(t, err) 840 require.Contains(t, httpError.Error, "capture not exists, non-exist-capture") 841 } 842 843 func TestListProcessor(t *testing.T) { 844 t.Parallel() 845 ctrl := gomock.NewController(t) 846 mo := mock_owner.NewMockOwner(ctrl) 847 controller := mock2.NewMockController(ctrl) 848 cp := capture.NewCaptureWithController4Test(mo, controller) 849 statusProvider := newStatusProvider() 850 router := newRouter(cp, statusProvider) 851 controller.EXPECT().GetProcessors(gomock.Any()).Return(statusProvider.GetProcessors(context.TODO())) 852 // test list processor succeeded 853 api := testCase{url: "/api/v1/processors", method: "GET"} 854 w := httptest.NewRecorder() 855 req, _ := http.NewRequestWithContext(context.Background(), api.method, api.url, nil) 856 router.ServeHTTP(w, req) 857 require.Equal(t, 200, w.Code) 858 var resp []model.ProcessorCommonInfo 859 err := json.NewDecoder(w.Body).Decode(&resp) 860 require.Nil(t, err) 861 require.Equal(t, changeFeedID, model.DefaultChangeFeedID(resp[0].CfID)) 862 } 863 864 func TestListCapture(t *testing.T) { 865 t.Parallel() 866 ctrl := gomock.NewController(t) 867 mo := mock_owner.NewMockOwner(ctrl) 868 etcdClient := mock_etcd.NewMockCDCEtcdClient(ctrl) 869 etcdClient.EXPECT().GetClusterID().Return("abcd").AnyTimes() 870 controller := mock2.NewMockController(ctrl) 871 cp := capture.NewCaptureWithController4Test(mo, controller) 872 cp.EtcdClient = etcdClient 873 statusProvider := newStatusProvider() 874 router := newRouter(cp, statusProvider) 875 controller.EXPECT().GetCaptures(gomock.Any()).Return(statusProvider.GetCaptures(context.TODO())) 876 // test list processor succeeded 877 api := testCase{url: "/api/v1/captures", method: "GET"} 878 w := httptest.NewRecorder() 879 req, _ := http.NewRequestWithContext(context.Background(), api.method, api.url, nil) 880 router.ServeHTTP(w, req) 881 require.Equal(t, 200, w.Code) 882 var resp []model.Capture 883 err := json.NewDecoder(w.Body).Decode(&resp) 884 require.Nil(t, err) 885 require.Equal(t, captureID, resp[0].ID) 886 } 887 888 func TestServerStatus(t *testing.T) { 889 t.Parallel() 890 // capture is owner 891 ctrl := gomock.NewController(t) 892 mo := mock_owner.NewMockOwner(ctrl) 893 controller := mock2.NewMockController(ctrl) 894 cp := capture.NewCaptureWithController4Test(mo, controller) 895 etcdClient := mock_etcd.NewMockCDCEtcdClient(ctrl) 896 etcdClient.EXPECT().GetClusterID().Return("abcd").AnyTimes() 897 ownerRouter := newRouter(cp, newStatusProvider()) 898 cp.EtcdClient = etcdClient 899 api := testCase{url: "/api/v1/status", method: "GET"} 900 w := httptest.NewRecorder() 901 req, _ := http.NewRequestWithContext(context.Background(), api.method, api.url, nil) 902 ownerRouter.ServeHTTP(w, req) 903 require.Equal(t, 200, w.Code) 904 var resp model.ServerStatus 905 err := json.NewDecoder(w.Body).Decode(&resp) 906 require.Nil(t, err) 907 require.Equal(t, "capture-for-test", resp.ID) 908 require.True(t, resp.IsOwner) 909 require.Equal(t, "abcd", resp.ClusterID) 910 911 // capture is not owner 912 c := capture.NewCapture4Test(nil) 913 c.EtcdClient = etcdClient 914 r := gin.New() 915 RegisterOpenAPIRoutes(r, NewOpenAPI4Test(c, nil)) 916 api = testCase{url: "/api/v1/status", method: "GET"} 917 w = httptest.NewRecorder() 918 req, _ = http.NewRequestWithContext(context.Background(), api.method, api.url, nil) 919 r.ServeHTTP(w, req) 920 require.Equal(t, 200, w.Code) 921 resp = model.ServerStatus{} 922 err = json.NewDecoder(w.Body).Decode(&resp) 923 require.Nil(t, err) 924 require.False(t, resp.IsOwner) 925 require.Equal(t, "abcd", resp.ClusterID) 926 } 927 928 func TestServerStatusLiveness(t *testing.T) { 929 t.Parallel() 930 // capture is owner 931 ctrl := gomock.NewController(t) 932 cp := mock_capture.NewMockCapture(ctrl) 933 etcdClient := mock_etcd.NewMockCDCEtcdClient(ctrl) 934 etcdClient.EXPECT().GetClusterID().Return("abcd").AnyTimes() 935 ownerRouter := newRouter(cp, newStatusProvider()) 936 api := testCase{url: "/api/v1/status", method: "GET"} 937 938 cp.EXPECT().IsReady().Return(true).AnyTimes() 939 cp.EXPECT().Info().DoAndReturn(func() (model.CaptureInfo, error) { 940 return model.CaptureInfo{}, nil 941 }).AnyTimes() 942 cp.EXPECT().IsController().DoAndReturn(func() bool { 943 return true 944 }).AnyTimes() 945 cp.EXPECT().GetEtcdClient().Return(etcdClient).AnyTimes() 946 947 // Alive. 948 alive := cp.EXPECT().Liveness().DoAndReturn(func() model.Liveness { 949 return model.LivenessCaptureAlive 950 }) 951 w := httptest.NewRecorder() 952 req, _ := http.NewRequestWithContext(context.Background(), api.method, api.url, nil) 953 ownerRouter.ServeHTTP(w, req) 954 require.Equal(t, 200, w.Code) 955 var resp model.ServerStatus 956 err := json.NewDecoder(w.Body).Decode(&resp) 957 require.Nil(t, err) 958 require.EqualValues(t, model.LivenessCaptureAlive, resp.Liveness) 959 960 // Draining the capture. 961 cp.EXPECT().Liveness().DoAndReturn(func() model.Liveness { 962 return model.LivenessCaptureStopping 963 }).After(alive) 964 w = httptest.NewRecorder() 965 req, _ = http.NewRequestWithContext(context.Background(), api.method, api.url, nil) 966 ownerRouter.ServeHTTP(w, req) 967 require.Equal(t, 200, w.Code) 968 err = json.NewDecoder(w.Body).Decode(&resp) 969 require.Nil(t, err) 970 require.EqualValues(t, model.LivenessCaptureStopping, resp.Liveness) 971 } 972 973 func TestSetLogLevel(t *testing.T) { 974 t.Parallel() 975 976 // test set log level succeeded 977 data := struct { 978 Level string `json:"log_level"` 979 }{"warn"} 980 ctrl := gomock.NewController(t) 981 mo := mock_owner.NewMockOwner(ctrl) 982 cp := capture.NewCapture4Test(mo) 983 router := newRouter(cp, newStatusProvider()) 984 api := testCase{url: "/api/v1/log", method: "POST"} 985 w := httptest.NewRecorder() 986 b, err := json.Marshal(&data) 987 require.Nil(t, err) 988 body := bytes.NewReader(b) 989 req, _ := http.NewRequestWithContext(context.Background(), api.method, api.url, body) 990 router.ServeHTTP(w, req) 991 require.Equal(t, 200, w.Code) 992 993 // test set log level failed 994 data = struct { 995 Level string `json:"log_level"` 996 }{"foo"} 997 api = testCase{url: "/api/v1/log", method: "POST"} 998 w = httptest.NewRecorder() 999 b, err = json.Marshal(&data) 1000 require.Nil(t, err) 1001 body = bytes.NewReader(b) 1002 req, _ = http.NewRequestWithContext(context.Background(), api.method, api.url, body) 1003 router.ServeHTTP(w, req) 1004 require.Equal(t, 400, w.Code) 1005 httpError := &model.HTTPError{} 1006 err = json.NewDecoder(w.Body).Decode(httpError) 1007 require.Nil(t, err) 1008 require.Contains(t, httpError.Error, "fail to change log level: foo") 1009 } 1010 1011 func TestHealth(t *testing.T) { 1012 t.Parallel() 1013 // capture is owner 1014 ctrl := gomock.NewController(t) 1015 cp := mock_capture.NewMockCapture(ctrl) 1016 1017 api := testCase{url: "/api/v1/health", method: "GET"} 1018 sp := mock_owner.NewMockStatusProvider(ctrl) 1019 ownerRouter := newRouter(cp, sp) 1020 1021 cp.EXPECT().IsReady().Return(true).AnyTimes() 1022 cp.EXPECT().Info().DoAndReturn(func() (model.CaptureInfo, error) { 1023 return model.CaptureInfo{}, nil 1024 }).AnyTimes() 1025 cp.EXPECT().IsController().DoAndReturn(func() bool { 1026 return true 1027 }).AnyTimes() 1028 1029 // IsHealthy returns error. 1030 isHealthError := sp.EXPECT().IsHealthy(gomock.Any()). 1031 Return(false, cerror.ErrOwnerNotFound) 1032 w := httptest.NewRecorder() 1033 req, _ := http.NewRequestWithContext(context.Background(), api.method, api.url, nil) 1034 ownerRouter.ServeHTTP(w, req) 1035 require.Equal(t, 500, w.Code) 1036 1037 // IsHealthy returns false. 1038 isHealthFalse := sp.EXPECT().IsHealthy(gomock.Any()). 1039 Return(false, cerror.ErrOwnerNotFound).After(isHealthError) 1040 w = httptest.NewRecorder() 1041 req, _ = http.NewRequestWithContext(context.Background(), api.method, api.url, nil) 1042 ownerRouter.ServeHTTP(w, req) 1043 require.Equal(t, 500, w.Code) 1044 1045 // IsHealthy returns true. 1046 sp.EXPECT().IsHealthy(gomock.Any()). 1047 Return(true, nil).After(isHealthFalse) 1048 w = httptest.NewRecorder() 1049 req, _ = http.NewRequestWithContext(context.Background(), api.method, api.url, nil) 1050 ownerRouter.ServeHTTP(w, req) 1051 require.Equal(t, 200, w.Code) 1052 } 1053 1054 // TODO: finished these test cases after we decouple those APIs from etcdClient. 1055 func TestCreateChangefeed(t *testing.T) {} 1056 func TestUpdateChangefeed(t *testing.T) {}