github.com/pingcap/tiflow@v0.0.0-20240520035814-5bf52d54e205/pkg/cmd/util/helper_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 util 15 16 import ( 17 "bytes" 18 "fmt" 19 "net/url" 20 "os" 21 "path/filepath" 22 "syscall" 23 "testing" 24 "time" 25 26 "github.com/pingcap/tiflow/pkg/config" 27 "github.com/pingcap/tiflow/pkg/util" 28 "github.com/spf13/cobra" 29 "github.com/stretchr/testify/require" 30 ) 31 32 func TestProxyFields(t *testing.T) { 33 revIndex := map[string]int{ 34 "http_proxy": 0, 35 "https_proxy": 1, 36 "no_proxy": 2, 37 } 38 envs := []string{"http_proxy", "https_proxy", "no_proxy"} 39 envPreset := []string{"http://127.0.0.1:8080", "https://127.0.0.1:8443", "localhost,127.0.0.1"} 40 41 // Exhaust all combinations of those environment variables' selection. 42 // Each bit of the mask decided whether this index of `envs` would be set. 43 for mask := 0; mask <= 0b111; mask++ { 44 for _, env := range envs { 45 require.Nil(t, os.Unsetenv(env)) 46 } 47 48 for i := 0; i < 3; i++ { 49 if (1<<i)&mask != 0 { 50 require.Nil(t, os.Setenv(envs[i], envPreset[i])) 51 } 52 } 53 54 for _, field := range findProxyFields() { 55 idx, ok := revIndex[field.Key] 56 require.True(t, ok) 57 require.NotEqual(t, 0, (1<<idx)&mask) 58 require.Equal(t, field.String, envPreset[idx]) 59 } 60 } 61 } 62 63 func TestVerifyPdEndpoint(t *testing.T) { 64 // empty URL. 65 url := "" 66 require.Regexp(t, ".*PD endpoint should be a valid http or https URL.*", 67 VerifyPdEndpoint(url, false)) 68 69 // invalid URL. 70 url = "\n hi" 71 require.Regexp(t, ".*invalid control character in URL.*", 72 VerifyPdEndpoint(url, false)) 73 74 // http URL without host. 75 url = "http://" 76 require.Regexp(t, ".*PD endpoint should be a valid http or https URL.*", 77 VerifyPdEndpoint(url, false)) 78 79 // https URL without host. 80 url = "https://" 81 require.Regexp(t, ".*PD endpoint should be a valid http or https URL.*", 82 VerifyPdEndpoint(url, false)) 83 84 // postgres scheme. 85 url = "postgres://postgres@localhost/cargo_registry" 86 require.Regexp(t, ".*PD endpoint should be a valid http or https URL.*", 87 VerifyPdEndpoint(url, false)) 88 89 // https scheme without TLS. 90 url = "https://aa" 91 require.Regexp(t, ".*PD endpoint scheme is https, please provide certificate.*", 92 VerifyPdEndpoint(url, false)) 93 94 // http scheme with TLS. 95 url = "http://aa" 96 require.Regexp(t, ".*PD endpoint scheme should be https.*", VerifyPdEndpoint(url, true)) 97 98 // valid http URL. 99 require.Nil(t, VerifyPdEndpoint("http://aa", false)) 100 101 // valid https URL with TLS. 102 require.Nil(t, VerifyPdEndpoint("https://aa", true)) 103 } 104 105 func TestStrictDecodeValidFile(t *testing.T) { 106 dataDir := t.TempDir() 107 tmpDir := t.TempDir() 108 109 configPath := filepath.Join(tmpDir, "ticdc.toml") 110 configContent := fmt.Sprintf(` 111 addr = "128.0.0.1:1234" 112 advertise-addr = "127.0.0.1:1111" 113 114 log-file = "/root/cdc1.log" 115 log-level = "warn" 116 117 data-dir = "%+v" 118 gc-ttl = 500 119 tz = "US" 120 capture-session-ttl = 10 121 122 owner-flush-interval = "600ms" 123 processor-flush-interval = "600ms" 124 125 [log.file] 126 max-size = 200 127 max-days = 1 128 max-backups = 1 129 130 [sorter] 131 sort-dir = "/tmp/just_a_test" 132 133 [security] 134 ca-path = "aa" 135 cert-path = "bb" 136 key-path = "cc" 137 cert-allowed-cn = ["dd","ee"] 138 `, dataDir) 139 err := os.WriteFile(configPath, []byte(configContent), 0o644) 140 require.Nil(t, err) 141 142 conf := config.GetDefaultServerConfig() 143 err = StrictDecodeFile(configPath, "test", conf) 144 require.Nil(t, err) 145 } 146 147 func TestStrictDecodeInvalidFile(t *testing.T) { 148 dataDir := t.TempDir() 149 tmpDir := t.TempDir() 150 151 configPath := filepath.Join(tmpDir, "ticdc.toml") 152 configContent := fmt.Sprintf(` 153 unknown = "128.0.0.1:1234" 154 data-dir = "%+v" 155 156 [log.unkown] 157 max-size = 200 158 max-days = 1 159 max-backups = 1 160 `, dataDir) 161 err := os.WriteFile(configPath, []byte(configContent), 0o644) 162 require.Nil(t, err) 163 164 conf := config.GetDefaultServerConfig() 165 err = StrictDecodeFile(configPath, "test", conf) 166 require.Contains(t, err.Error(), "contained unknown configuration options") 167 } 168 169 func TestAndWriteExampleReplicaTOML(t *testing.T) { 170 cfg := config.GetDefaultReplicaConfig() 171 err := StrictDecodeFile("changefeed.toml", "cdc", &cfg) 172 require.Nil(t, err) 173 174 require.True(t, cfg.CaseSensitive) 175 require.Equal(t, &config.FilterConfig{ 176 IgnoreTxnStartTs: []uint64{1, 2}, 177 Rules: []string{"*.*", "!test.*"}, 178 }, cfg.Filter) 179 require.Equal(t, &config.MounterConfig{ 180 WorkerNum: 16, 181 }, cfg.Mounter) 182 183 sinkURL, err := url.Parse("kafka://127.0.0.1:9092") 184 require.NoError(t, err) 185 186 err = cfg.ValidateAndAdjust(sinkURL) 187 require.NoError(t, err) 188 require.Equal(t, &config.SinkConfig{ 189 EncoderConcurrency: util.AddressOf(config.DefaultEncoderGroupConcurrency), 190 DispatchRules: []*config.DispatchRule{ 191 {PartitionRule: "ts", TopicRule: "hello_{schema}", Matcher: []string{"test1.*", "test2.*"}}, 192 {PartitionRule: "rowid", TopicRule: "{schema}_world", Matcher: []string{"test3.*", "test4.*"}}, 193 }, 194 ColumnSelectors: []*config.ColumnSelector{ 195 {Matcher: []string{"test1.*", "test2.*"}, Columns: []string{"column1", "column2"}}, 196 {Matcher: []string{"test3.*", "test4.*"}, Columns: []string{"!a", "column3"}}, 197 }, 198 CSVConfig: &config.CSVConfig{ 199 Quote: string(config.DoubleQuoteChar), 200 Delimiter: string(config.Comma), 201 NullString: config.NULL, 202 BinaryEncodingMethod: config.BinaryEncodingBase64, 203 }, 204 Terminator: util.AddressOf("\r\n"), 205 DateSeparator: util.AddressOf(config.DateSeparatorDay.String()), 206 EnablePartitionSeparator: util.AddressOf(true), 207 EnableKafkaSinkV2: util.AddressOf(false), 208 OnlyOutputUpdatedColumns: util.AddressOf(false), 209 DeleteOnlyOutputHandleKeyColumns: util.AddressOf(false), 210 ContentCompatible: util.AddressOf(false), 211 Protocol: util.AddressOf("open-protocol"), 212 AdvanceTimeoutInSec: util.AddressOf(uint(150)), 213 SendBootstrapIntervalInSec: util.AddressOf(int64(120)), 214 SendBootstrapInMsgCount: util.AddressOf(int32(10000)), 215 SendBootstrapToAllPartition: util.AddressOf(true), 216 DebeziumDisableSchema: util.AddressOf(false), 217 OpenProtocol: &config.OpenProtocolConfig{OutputOldValue: true}, 218 Debezium: &config.DebeziumConfig{OutputOldValue: true}, 219 }, cfg.Sink) 220 } 221 222 func TestAndWriteStorageSinkTOML(t *testing.T) { 223 cfg := config.GetDefaultReplicaConfig() 224 err := StrictDecodeFile("changefeed_storage_sink.toml", "cdc", &cfg) 225 require.NoError(t, err) 226 227 sinkURL, err := url.Parse("s3://127.0.0.1:9092") 228 require.NoError(t, err) 229 230 cfg.Sink.Protocol = util.AddressOf(config.ProtocolCanalJSON.String()) 231 err = cfg.ValidateAndAdjust(sinkURL) 232 require.NoError(t, err) 233 require.Equal(t, &config.SinkConfig{ 234 Protocol: util.AddressOf(config.ProtocolCanalJSON.String()), 235 EncoderConcurrency: util.AddressOf(config.DefaultEncoderGroupConcurrency), 236 Terminator: util.AddressOf(config.CRLF), 237 TxnAtomicity: util.AddressOf(config.AtomicityLevel("")), 238 DateSeparator: util.AddressOf("day"), 239 EnablePartitionSeparator: util.AddressOf(true), 240 FileIndexWidth: util.AddressOf(config.DefaultFileIndexWidth), 241 EnableKafkaSinkV2: util.AddressOf(false), 242 CSVConfig: &config.CSVConfig{ 243 Delimiter: ",", 244 Quote: "\"", 245 NullString: "\\N", 246 IncludeCommitTs: false, 247 BinaryEncodingMethod: config.BinaryEncodingBase64, 248 }, 249 OnlyOutputUpdatedColumns: util.AddressOf(false), 250 DeleteOnlyOutputHandleKeyColumns: util.AddressOf(false), 251 ContentCompatible: util.AddressOf(false), 252 AdvanceTimeoutInSec: util.AddressOf(uint(150)), 253 SendBootstrapIntervalInSec: util.AddressOf(int64(120)), 254 SendBootstrapInMsgCount: util.AddressOf(int32(10000)), 255 SendBootstrapToAllPartition: util.AddressOf(true), 256 DebeziumDisableSchema: util.AddressOf(false), 257 OpenProtocol: &config.OpenProtocolConfig{OutputOldValue: true}, 258 Debezium: &config.DebeziumConfig{OutputOldValue: true}, 259 }, cfg.Sink) 260 } 261 262 func TestAndWriteExampleServerTOML(t *testing.T) { 263 cfg := config.GetDefaultServerConfig() 264 err := StrictDecodeFile("ticdc.toml", "cdc", &cfg) 265 require.Nil(t, err) 266 defcfg := config.GetDefaultServerConfig() 267 defcfg.AdvertiseAddr = "127.0.0.1:8300" 268 defcfg.LogFile = "/tmp/ticdc/ticdc.log" 269 require.Equal(t, defcfg, cfg) 270 } 271 272 func TestJSONPrint(t *testing.T) { 273 cmd := new(cobra.Command) 274 type testStruct struct { 275 A string `json:"a"` 276 } 277 278 data := testStruct{ 279 A: "string", 280 } 281 282 var b bytes.Buffer 283 cmd.SetOut(&b) 284 285 err := JSONPrint(cmd, &data) 286 require.Nil(t, err) 287 288 output := `{ 289 "a": "string" 290 } 291 ` 292 require.Equal(t, output, b.String()) 293 } 294 295 func TestIgnoreStrictCheckItem(t *testing.T) { 296 dataDir := t.TempDir() 297 tmpDir := t.TempDir() 298 299 configPath := filepath.Join(tmpDir, "ticdc.toml") 300 configContent := fmt.Sprintf(` 301 data-dir = "%+v" 302 [unknown] 303 max-size = 200 304 max-days = 1 305 max-backups = 1 306 `, dataDir) 307 err := os.WriteFile(configPath, []byte(configContent), 0o644) 308 require.Nil(t, err) 309 310 conf := config.GetDefaultServerConfig() 311 err = StrictDecodeFile(configPath, "test", conf, "unknown") 312 require.Nil(t, err) 313 314 configContent = fmt.Sprintf(` 315 data-dir = "%+v" 316 [unknown] 317 max-size = 200 318 max-days = 1 319 max-backups = 1 320 [unknown2] 321 max-size = 200 322 max-days = 1 323 max-backups = 1 324 `, dataDir) 325 err = os.WriteFile(configPath, []byte(configContent), 0o644) 326 require.Nil(t, err) 327 328 err = StrictDecodeFile(configPath, "test", conf, "unknown") 329 require.Contains(t, err.Error(), "contained unknown configuration options: unknown2") 330 331 configContent = fmt.Sprintf(` 332 data-dir = "%+v" 333 [debug] 334 unknown = 1 335 `, dataDir) 336 err = os.WriteFile(configPath, []byte(configContent), 0o644) 337 require.Nil(t, err) 338 339 err = StrictDecodeFile(configPath, "test", conf, "debug") 340 require.Nil(t, err) 341 } 342 343 func TestInitSignalHandlingGracefulShutdown(t *testing.T) { 344 shutdownCh := make(chan struct{}, 1) 345 shutdown := func() <-chan struct{} { return shutdownCh } 346 cancelCh := make(chan struct{}, 1) 347 cancel := func() { cancelCh <- struct{}{} } 348 InitSignalHandling(shutdown, cancel) 349 self, err := os.FindProcess(os.Getpid()) 350 require.Nil(t, err) 351 352 // First signal for preparing shutdown. 353 err = self.Signal(syscall.SIGTERM) 354 require.Nil(t, err) 355 select { 356 case <-shutdownCh: 357 require.Fail(t, "unexpected") 358 case <-cancelCh: 359 require.Fail(t, "unexpected") 360 case <-time.After(100 * time.Millisecond): 361 } 362 363 // Graceful shutdown complete. 364 shutdownCh <- struct{}{} 365 select { 366 case <-cancelCh: 367 case <-time.After(100 * time.Millisecond): 368 require.Fail(t, "timeout") 369 } 370 } 371 372 func TestInitSignalHandlingForceShutdown(t *testing.T) { 373 shutdownCh := make(chan struct{}, 1) 374 shutdown := func() <-chan struct{} { return shutdownCh } 375 cancelCh := make(chan struct{}, 1) 376 cancel := func() { cancelCh <- struct{}{} } 377 InitSignalHandling(shutdown, cancel) 378 self, err := os.FindProcess(os.Getpid()) 379 require.Nil(t, err) 380 err = self.Signal(syscall.SIGTERM) 381 require.Nil(t, err) 382 // Second signal for force shutdown. 383 // We use another signal, to avoid lost signal, because sending a signal 384 // is setting a bit in Unix. 385 err = self.Signal(syscall.SIGQUIT) 386 require.Nil(t, err) 387 select { 388 case <-cancelCh: 389 case <-time.After(1 * time.Second): 390 require.Fail(t, "timeout") 391 } 392 }