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) {}