github.com/pingcap/tiflow@v0.0.0-20240520035814-5bf52d54e205/tests/integration_tests/api_v2/cases.go (about)

     1  // Copyright 2023 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 main
    15  
    16  import (
    17  	"bytes"
    18  	"context"
    19  	"encoding/json"
    20  	"reflect"
    21  	"time"
    22  
    23  	"github.com/pingcap/log"
    24  	"github.com/pingcap/tiflow/pkg/config"
    25  	"github.com/pingcap/tiflow/pkg/util"
    26  	"go.uber.org/zap"
    27  )
    28  
    29  // customReplicaConfig some custom fake configs to test the compatibility
    30  var customReplicaConfig = &ReplicaConfig{
    31  	MemoryQuota:           1123450,
    32  	CaseSensitive:         false,
    33  	ForceReplicate:        false,
    34  	IgnoreIneligibleTable: false,
    35  	CheckGCSafePoint:      false,
    36  	BDRMode:               util.AddressOf(false),
    37  	EnableSyncPoint:       util.AddressOf(false),
    38  	SyncPointInterval:     util.AddressOf(JSONDuration{duration: 10 * time.Minute}),
    39  	SyncPointRetention:    util.AddressOf(JSONDuration{duration: 24 * time.Hour}),
    40  	Filter: &FilterConfig{
    41  		IgnoreTxnStartTs: []uint64{1, 2, 3},
    42  		EventFilters: []EventFilterRule{{
    43  			Matcher:                  []string{"test.worker"},
    44  			IgnoreEvent:              []string{"update"},
    45  			IgnoreSQL:                []string{"^drop", "add column"},
    46  			IgnoreInsertValueExpr:    "id >= 100",
    47  			IgnoreUpdateNewValueExpr: "gender = 'male'",
    48  			IgnoreUpdateOldValueExpr: "age < 18",
    49  			IgnoreDeleteValueExpr:    "id > 100",
    50  		}},
    51  		Rules: []string{
    52  			"a.d", "b.x",
    53  		},
    54  	},
    55  	Mounter: &MounterConfig{
    56  		WorkerNum: 17,
    57  	},
    58  	Sink: &SinkConfig{
    59  		Protocol: "arvo",
    60  		ColumnSelectors: []*ColumnSelector{
    61  			{
    62  				[]string{"a.b"},
    63  				[]string{"c"},
    64  			},
    65  		},
    66  		TxnAtomicity: "table",
    67  		Terminator:   "a",
    68  		CSVConfig: &CSVConfig{
    69  			Quote:      string(config.DoubleQuoteChar),
    70  			Delimiter:  config.Comma,
    71  			NullString: config.NULL,
    72  		},
    73  		DateSeparator:               "day",
    74  		EncoderConcurrency:          util.AddressOf(32),
    75  		EnablePartitionSeparator:    util.AddressOf(true),
    76  		ContentCompatible:           util.AddressOf(true),
    77  		SendBootstrapIntervalInSec:  util.AddressOf(int64(120)),
    78  		SendBootstrapInMsgCount:     util.AddressOf(int32(10000)),
    79  		SendBootstrapToAllPartition: util.AddressOf(true),
    80  		DebeziumDisableSchema:       util.AddressOf(true),
    81  		OpenProtocolConfig:          &OpenProtocolConfig{OutputOldValue: true},
    82  		DebeziumConfig:              &DebeziumConfig{OutputOldValue: true},
    83  	},
    84  	Scheduler: &ChangefeedSchedulerConfig{
    85  		EnableTableAcrossNodes: false,
    86  		RegionThreshold:        13,
    87  	},
    88  	Integrity: &IntegrityConfig{
    89  		IntegrityCheckLevel:   "none",
    90  		CorruptionHandleLevel: "warn",
    91  	},
    92  	Consistent: &ConsistentConfig{
    93  		Level:                 "none",
    94  		MaxLogSize:            64,
    95  		FlushIntervalInMs:     2000,
    96  		MetaFlushIntervalInMs: 200,
    97  		Storage:               "",
    98  		UseFileBackend:        false,
    99  		EncoderWorkerNum:      31,
   100  		FlushWorkerNum:        18,
   101  	},
   102  }
   103  
   104  // defaultReplicaConfig check if the default values is changed
   105  var defaultReplicaConfig = &ReplicaConfig{
   106  	MemoryQuota:        1024 * 1024 * 1024,
   107  	CaseSensitive:      false,
   108  	CheckGCSafePoint:   true,
   109  	EnableSyncPoint:    util.AddressOf(false),
   110  	SyncPointInterval:  util.AddressOf(JSONDuration{duration: 10 * time.Minute}),
   111  	SyncPointRetention: util.AddressOf(JSONDuration{duration: 24 * time.Hour}),
   112  	BDRMode:            util.AddressOf(false),
   113  	Filter: &FilterConfig{
   114  		Rules: []string{"*.*"},
   115  	},
   116  	Mounter: &MounterConfig{
   117  		WorkerNum: 16,
   118  	},
   119  	Sink: &SinkConfig{
   120  		CSVConfig: &CSVConfig{
   121  			Quote:      string(config.DoubleQuoteChar),
   122  			Delimiter:  config.Comma,
   123  			NullString: config.NULL,
   124  		},
   125  		Terminator:                  "\r\n",
   126  		DateSeparator:               "day",
   127  		EncoderConcurrency:          util.AddressOf(32),
   128  		EnablePartitionSeparator:    util.AddressOf(true),
   129  		ContentCompatible:           util.AddressOf(false),
   130  		SendBootstrapIntervalInSec:  util.AddressOf(int64(120)),
   131  		SendBootstrapInMsgCount:     util.AddressOf(int32(10000)),
   132  		SendBootstrapToAllPartition: util.AddressOf(true),
   133  		DebeziumDisableSchema:       util.AddressOf(false),
   134  		OpenProtocolConfig:          &OpenProtocolConfig{OutputOldValue: true},
   135  		DebeziumConfig:              &DebeziumConfig{OutputOldValue: true},
   136  	},
   137  	Scheduler: &ChangefeedSchedulerConfig{
   138  		EnableTableAcrossNodes: false,
   139  		RegionThreshold:        100_000,
   140  	},
   141  	Integrity: &IntegrityConfig{
   142  		IntegrityCheckLevel:   "none",
   143  		CorruptionHandleLevel: "warn",
   144  	},
   145  	Consistent: &ConsistentConfig{
   146  		Level:                 "none",
   147  		MaxLogSize:            64,
   148  		FlushIntervalInMs:     2000,
   149  		MetaFlushIntervalInMs: 200,
   150  		EncoderWorkerNum:      16,
   151  		FlushWorkerNum:        8,
   152  		Storage:               "",
   153  		UseFileBackend:        false,
   154  	},
   155  }
   156  
   157  func testStatus(ctx context.Context, client *CDCRESTClient) error {
   158  	resp := client.Get().WithURI("/status").Do(ctx)
   159  	assertResponseIsOK(resp)
   160  	if err := json.Unmarshal(resp.body, &ServerStatus{}); err != nil {
   161  		log.Panic("unmarshal failed", zap.String("body", string(resp.body)), zap.Error(err))
   162  	}
   163  	println("pass test: get status")
   164  	return nil
   165  }
   166  
   167  func testClusterHealth(ctx context.Context, client *CDCRESTClient) error {
   168  	resp := client.Get().WithURI("/health").Do(ctx)
   169  	assertResponseIsOK(resp)
   170  	assertEmptyResponseBody(resp)
   171  	println("pass test: health")
   172  	return nil
   173  }
   174  
   175  func testChangefeed(ctx context.Context, client *CDCRESTClient) error {
   176  	// changefeed with default value
   177  	data := `{
   178  		"changefeed_id": "changefeed-test-v2-black-hole-1",
   179  		"sink_uri": "blackhole://",
   180          "namespace": "test"
   181  	}`
   182  	resp := client.Post().
   183  		WithBody(bytes.NewReader([]byte(data))).
   184  		WithURI("/changefeeds").
   185  		Do(ctx)
   186  	assertResponseIsOK(resp)
   187  	changefeedInfo1 := &ChangeFeedInfo{}
   188  	if err := json.Unmarshal(resp.body, changefeedInfo1); err != nil {
   189  		log.Panic("unmarshal failed", zap.String("body", string(resp.body)), zap.Error(err))
   190  	}
   191  
   192  	ensureChangefeed(ctx, client, changefeedInfo1.ID, "normal")
   193  	resp = client.Get().WithURI("/changefeeds/" + changefeedInfo1.ID + "?namespace=test").Do(ctx)
   194  	assertResponseIsOK(resp)
   195  	cfInfo := &ChangeFeedInfo{}
   196  	if err := json.Unmarshal(resp.body, cfInfo); err != nil {
   197  		log.Panic("failed to unmarshal response", zap.String("body", string(resp.body)), zap.Error(err))
   198  	}
   199  	if !reflect.DeepEqual(cfInfo.Config, defaultReplicaConfig) {
   200  		log.Panic("config is not equals",
   201  			zap.Any("add", defaultReplicaConfig),
   202  			zap.Any("get", cfInfo.Config))
   203  	}
   204  
   205  	// pause changefeed
   206  	resp = client.Post().WithURI("changefeeds/changefeed-test-v2-black-hole-1/pause?namespace=test").Do(ctx)
   207  	assertResponseIsOK(resp)
   208  	assertEmptyResponseBody(resp)
   209  
   210  	ensureChangefeed(ctx, client, changefeedInfo1.ID, "stopped")
   211  
   212  	// update changefeed
   213  	data = `{
   214  		"sink_uri": "blackhole://?aa=bb",
   215  		"replica_config":{
   216  			"ignore_ineligible_table": true
   217  		}
   218  	}`
   219  	resp = client.Put().
   220  		WithBody(bytes.NewReader([]byte(data))).
   221  		WithURI("/changefeeds/changefeed-test-v2-black-hole-1?namespace=test").
   222  		Do(ctx)
   223  	assertResponseIsOK(resp)
   224  	changefeedInfo1 = &ChangeFeedInfo{}
   225  	if err := json.Unmarshal(resp.body, changefeedInfo1); err != nil {
   226  		log.Panic("unmarshal failed", zap.String("body", string(resp.body)), zap.Error(err))
   227  	}
   228  
   229  	// update with full custom config
   230  	newConfig := &ChangefeedConfig{
   231  		ReplicaConfig: customReplicaConfig,
   232  	}
   233  	cdata, err := json.Marshal(newConfig)
   234  	if err != nil {
   235  		log.Panic("marshal failed", zap.Error(err))
   236  	}
   237  	resp = client.Put().
   238  		WithBody(bytes.NewReader(cdata)).
   239  		WithURI("/changefeeds/changefeed-test-v2-black-hole-1?namespace=test").
   240  		Do(ctx)
   241  	assertResponseIsOK(resp)
   242  
   243  	resp = client.Get().WithURI("changefeeds/changefeed-test-v2-black-hole-1?namespace=test").Do(ctx)
   244  	assertResponseIsOK(resp)
   245  	cf := &ChangeFeedInfo{}
   246  	if err := json.Unmarshal(resp.body, cf); err != nil {
   247  		log.Panic("unmarshal failed", zap.String("body", string(resp.body)), zap.Error(err))
   248  	}
   249  	if !reflect.DeepEqual(cf.Config, customReplicaConfig) {
   250  		log.Panic("config is not equals",
   251  			zap.Any("update", customReplicaConfig),
   252  			zap.Any("get", cf.Config))
   253  	}
   254  
   255  	// list changefeed
   256  	resp = client.Get().WithURI("changefeeds?state=stopped&namespace=test").Do(ctx)
   257  	assertResponseIsOK(resp)
   258  	changefeedList := &ListResponse[ChangefeedCommonInfo]{}
   259  	if err := json.Unmarshal(resp.body, changefeedList); err != nil {
   260  		log.Panic("unmarshal failed", zap.String("body", string(resp.body)), zap.Error(err))
   261  	}
   262  	if len(changefeedList.Items) != 1 {
   263  		log.Panic("changefeed items is not equals to 1", zap.Any("list", changefeedList))
   264  	}
   265  
   266  	resp = client.Post().WithBody(bytes.NewReader(
   267  		[]byte(`{"overwrite_checkpoint_ts":0}`))).
   268  		WithURI("changefeeds/changefeed-test-v2-black-hole-1/resume?namespace=test").Do(ctx)
   269  	assertResponseIsOK(resp)
   270  	assertEmptyResponseBody(resp)
   271  
   272  	// check get changefeed
   273  	ensureChangefeed(ctx, client, changefeedInfo1.ID, "normal")
   274  
   275  	resp = client.Delete().
   276  		WithURI("changefeeds/changefeed-test-v2-black-hole-1?namespace=test").Do(ctx)
   277  	assertResponseIsOK(resp)
   278  	assertEmptyResponseBody(resp)
   279  
   280  	resp = client.Get().
   281  		WithURI("changefeeds/changefeed-test-v2-black-hole-1?namespace=test").Do(ctx)
   282  	if resp.statusCode == 200 {
   283  		log.Panic("delete changefeed failed", zap.Any("resp", resp))
   284  	}
   285  
   286  	println("pass test: changefeed apis")
   287  	return nil
   288  }
   289  
   290  func testCreateChangefeed(ctx context.Context, client *CDCRESTClient) error {
   291  	config := ChangefeedConfig{
   292  		ID:            "test-create-all",
   293  		Namespace:     "test",
   294  		SinkURI:       "blackhole://create=test",
   295  		ReplicaConfig: customReplicaConfig,
   296  	}
   297  	resp := client.Post().
   298  		WithBody(&config).
   299  		WithURI("/changefeeds").
   300  		Do(ctx)
   301  	assertResponseIsOK(resp)
   302  	ensureChangefeed(ctx, client, config.ID, "normal")
   303  	resp = client.Get().WithURI("/changefeeds/" + config.ID + "?namespace=test").Do(ctx)
   304  	assertResponseIsOK(resp)
   305  	cfInfo := &ChangeFeedInfo{}
   306  	if err := json.Unmarshal(resp.body, cfInfo); err != nil {
   307  		log.Panic("failed to unmarshal response", zap.String("body", string(resp.body)), zap.Error(err))
   308  	}
   309  	if !reflect.DeepEqual(cfInfo.Config, config.ReplicaConfig) {
   310  		log.Panic("config is not equals", zap.Any("add", config.ReplicaConfig), zap.Any("get", cfInfo.Config))
   311  	}
   312  	resp = client.Delete().WithURI("/changefeeds/" + config.ID + "?namespace=test").Do(ctx)
   313  	assertResponseIsOK(resp)
   314  	return nil
   315  }
   316  
   317  func testRemoveChangefeed(ctx context.Context, client *CDCRESTClient) error {
   318  	resp := client.Delete().WithURI("changefeeds/changefeed-not-exist").Do(ctx)
   319  	assertResponseIsOK(resp)
   320  	println("pass test: delete changefeed apis")
   321  	return nil
   322  }
   323  
   324  func testCapture(ctx context.Context, client *CDCRESTClient) error {
   325  	resp := client.Get().WithURI("captures").Do(ctx)
   326  	assertResponseIsOK(resp)
   327  	captures := &ListResponse[Capture]{}
   328  	if err := json.Unmarshal(resp.body, captures); err != nil {
   329  		log.Panic("unmarshal failed", zap.String("body", string(resp.body)), zap.Error(err))
   330  	}
   331  	if len(captures.Items) != 1 {
   332  		log.Panic("capture size is not 1", zap.Any("resp", resp))
   333  	}
   334  	println("pass test: capture apis")
   335  	return nil
   336  }
   337  
   338  func testProcessor(ctx context.Context, client *CDCRESTClient) error {
   339  	resp := client.Get().WithURI("processors").Do(ctx)
   340  	assertResponseIsOK(resp)
   341  	processors := &ListResponse[ProcessorCommonInfo]{}
   342  	if err := json.Unmarshal(resp.body, processors); err != nil {
   343  		log.Panic("unmarshal failed", zap.String("body", string(resp.body)), zap.Error(err))
   344  	}
   345  	if len(processors.Items) == 0 {
   346  		log.Panic("processor size is 0", zap.Any("resp", resp))
   347  	}
   348  
   349  	processorDetail := &ProcessorDetail{}
   350  	resp = client.Get().
   351  		WithURI("processors/" + processors.Items[0].ChangeFeedID + "/" +
   352  			processors.Items[0].CaptureID +
   353  			"?namespace=" + processors.Items[0].Namespace).
   354  		Do(ctx)
   355  	assertResponseIsOK(resp)
   356  	if err := json.Unmarshal(resp.body, processorDetail); err != nil {
   357  		log.Panic("unmarshal failed", zap.String("body", string(resp.body)), zap.Error(err))
   358  	}
   359  	println("pass test: processor apis")
   360  	return nil
   361  }
   362  
   363  func testResignOwner(ctx context.Context, client *CDCRESTClient) error {
   364  	resp := client.Post().WithURI("owner/resign").Do(ctx)
   365  	assertResponseIsOK(resp)
   366  	assertResponseIsOK(resp)
   367  	println("pass test: owner apis")
   368  	return nil
   369  }
   370  
   371  func testSetLogLevel(ctx context.Context, client *CDCRESTClient) error {
   372  	resp := client.Post().WithURI("/log").
   373  		WithBody(&LogLevelReq{Level: "debug"}).
   374  		Do(ctx)
   375  	assertResponseIsOK(resp)
   376  	assertEmptyResponseBody(resp)
   377  	client.Post().WithURI("/log").
   378  		WithBody(&LogLevelReq{Level: "info"}).
   379  		Do(ctx)
   380  	assertResponseIsOK(resp)
   381  	assertEmptyResponseBody(resp)
   382  	println("pass test: set log level")
   383  	return nil
   384  }
   385  
   386  func assertEmptyResponseBody(resp *Result) {
   387  	if "{}" != string(resp.body) {
   388  		log.Panic("failed call api", zap.String("body", string(resp.body)))
   389  	}
   390  }
   391  
   392  func assertResponseIsOK(resp *Result) {
   393  	if resp.err != nil {
   394  		log.Panic("failed call api", zap.Error(resp.Error()))
   395  	}
   396  	if resp.statusCode != 200 {
   397  		log.Panic("api status code is not 200", zap.Int("code", resp.statusCode))
   398  	}
   399  }
   400  
   401  func ensureChangefeed(ctx context.Context, client *CDCRESTClient, id, state string) {
   402  	var info *ChangeFeedInfo
   403  	for i := 0; i < 10; i++ {
   404  		resp := client.Get().
   405  			WithURI("/changefeeds/" + id + "?namespace=test").Do(ctx)
   406  		if resp.statusCode == 200 {
   407  			info = &ChangeFeedInfo{}
   408  			if err := json.Unmarshal(resp.body, info); err != nil {
   409  				log.Panic("unmarshal failed", zap.String("body", string(resp.body)), zap.Error(err))
   410  			}
   411  			if info.State == state {
   412  				return
   413  			}
   414  		}
   415  		log.Info("check changefeed failed", zap.Int("time", i), zap.Any("info", info))
   416  		time.Sleep(2 * time.Second)
   417  	}
   418  	log.Panic("ensure changefeed failed")
   419  }