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 }