github.com/pingcap/tiflow@v0.0.0-20240520035814-5bf52d54e205/dm/master/config_test.go (about) 1 // Copyright 2019 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 master 15 16 import ( 17 "crypto/rand" 18 "encoding/hex" 19 "flag" 20 "fmt" 21 "net/url" 22 "os" 23 "path" 24 "path/filepath" 25 "strings" 26 "testing" 27 28 capturer "github.com/kami-zh/go-capturer" 29 "github.com/pingcap/check" 30 "github.com/pingcap/tiflow/dm/pkg/log" 31 "github.com/pingcap/tiflow/dm/pkg/terror" 32 "github.com/stretchr/testify/require" 33 "go.etcd.io/etcd/server/v3/embed" 34 ) 35 36 var _ = check.Suite(&testConfigSuite{}) 37 38 type testConfigSuite struct{} 39 40 func (t *testConfigSuite) SetUpSuite(c *check.C) { 41 // initialized the logger to make genEmbedEtcdConfig working. 42 c.Assert(log.InitLogger(&log.Config{}), check.IsNil) 43 } 44 45 func (t *testConfigSuite) TestPrintSampleConfig(c *check.C) { 46 // test print sample config 47 out := capturer.CaptureStdout(func() { 48 cfg := NewConfig() 49 err := cfg.Parse([]string{"-print-sample-config"}) 50 c.Assert(err, check.ErrorMatches, flag.ErrHelp.Error()) 51 }) 52 c.Assert(strings.TrimSpace(out), check.Equals, strings.TrimSpace(SampleConfig)) 53 } 54 55 func (t *testConfigSuite) TestConfig(c *check.C) { 56 var ( 57 err error 58 cfg = &Config{} 59 masterAddr = ":8261" 60 cases = []struct { 61 args []string 62 hasError bool 63 errorReg string 64 }{ 65 { 66 []string{"-V"}, 67 true, 68 flag.ErrHelp.Error(), 69 }, 70 { 71 []string{"-print-sample-config"}, 72 true, 73 flag.ErrHelp.Error(), 74 }, 75 { 76 []string{"invalid"}, 77 true, 78 ".*'invalid' is an invalid flag.*", 79 }, 80 } 81 ) 82 83 err = cfg.FromContent(SampleConfig) 84 c.Assert(err, check.IsNil) 85 err = cfg.Reload() 86 c.Assert(err, check.IsNil) 87 c.Assert(cfg.MasterAddr, check.Equals, masterAddr) 88 89 for _, tc := range cases { 90 cfg = NewConfig() 91 err = cfg.Parse(tc.args) 92 if tc.hasError { 93 c.Assert(err, check.ErrorMatches, tc.errorReg) 94 } 95 } 96 } 97 98 func (t *testConfigSuite) TestInvalidConfig(c *check.C) { 99 var ( 100 err error 101 cfg = NewConfig() 102 ) 103 104 filepath := path.Join(c.MkDir(), "test_invalid_config.toml") 105 // field still remain undecoded in config will cause verify failed 106 configContent := []byte(` 107 master-addr = ":8261" 108 advertise-addr = "127.0.0.1:8261" 109 aaa = "xxx"`) 110 err = os.WriteFile(filepath, configContent, 0o644) 111 c.Assert(err, check.IsNil) 112 err = cfg.configFromFile(filepath) 113 c.Assert(err, check.NotNil) 114 c.Assert(err, check.ErrorMatches, "*master config contained unknown configuration options: aaa.*") 115 116 // invalid `master-addr` 117 filepath2 := path.Join(c.MkDir(), "test_invalid_config.toml") 118 configContent2 := []byte(`master-addr = ""`) 119 err = os.WriteFile(filepath2, configContent2, 0o644) 120 c.Assert(err, check.IsNil) 121 err = cfg.configFromFile(filepath2) 122 c.Assert(err, check.IsNil) 123 c.Assert(terror.ErrMasterHostPortNotValid.Equal(cfg.adjust()), check.IsTrue) 124 } 125 126 func (t *testConfigSuite) TestGenEmbedEtcdConfig(c *check.C) { 127 hostname, err := os.Hostname() 128 c.Assert(err, check.IsNil) 129 130 cfg1 := NewConfig() 131 cfg1.MasterAddr = ":8261" 132 cfg1.AdvertiseAddr = "127.0.0.1:8261" 133 cfg1.InitialClusterState = embed.ClusterStateFlagExisting 134 c.Assert(cfg1.adjust(), check.IsNil) 135 etcdCfg, err := cfg1.genEmbedEtcdConfig(embed.NewConfig()) 136 c.Assert(err, check.IsNil) 137 c.Assert(etcdCfg.Name, check.Equals, fmt.Sprintf("dm-master-%s", hostname)) 138 c.Assert(etcdCfg.Dir, check.Equals, fmt.Sprintf("default.%s", etcdCfg.Name)) 139 c.Assert(etcdCfg.ListenClientUrls, check.DeepEquals, []url.URL{{Scheme: "http", Host: "0.0.0.0:8261"}}) 140 c.Assert(etcdCfg.AdvertiseClientUrls, check.DeepEquals, []url.URL{{Scheme: "http", Host: "127.0.0.1:8261"}}) 141 c.Assert(etcdCfg.ListenPeerUrls, check.DeepEquals, []url.URL{{Scheme: "http", Host: "127.0.0.1:8291"}}) 142 c.Assert(etcdCfg.AdvertisePeerUrls, check.DeepEquals, []url.URL{{Scheme: "http", Host: "127.0.0.1:8291"}}) 143 c.Assert(etcdCfg.InitialCluster, check.DeepEquals, fmt.Sprintf("dm-master-%s=http://127.0.0.1:8291", hostname)) 144 c.Assert(etcdCfg.ClusterState, check.Equals, embed.ClusterStateFlagExisting) 145 c.Assert(etcdCfg.AutoCompactionMode, check.Equals, "periodic") 146 c.Assert(etcdCfg.AutoCompactionRetention, check.Equals, "1h") 147 c.Assert(etcdCfg.QuotaBackendBytes, check.Equals, int64(2*1024*1024*1024)) 148 149 cfg2 := *cfg1 150 cfg2.MasterAddr = "127.0.0.1\n:8261" 151 cfg2.AdvertiseAddr = "127.0.0.1:8261" 152 _, err = cfg2.genEmbedEtcdConfig(embed.NewConfig()) 153 c.Assert(terror.ErrMasterGenEmbedEtcdConfigFail.Equal(err), check.IsTrue) 154 c.Assert(err, check.ErrorMatches, "(?m).*invalid master-addr.*") 155 cfg2.MasterAddr = "172.100.8.8:8261" 156 cfg2.AdvertiseAddr = "172.100.8.8:8261" 157 etcdCfg, err = cfg2.genEmbedEtcdConfig(embed.NewConfig()) 158 c.Assert(err, check.IsNil) 159 c.Assert(etcdCfg.ListenClientUrls, check.DeepEquals, []url.URL{{Scheme: "http", Host: "172.100.8.8:8261"}}) 160 c.Assert(etcdCfg.AdvertiseClientUrls, check.DeepEquals, []url.URL{{Scheme: "http", Host: "172.100.8.8:8261"}}) 161 162 cfg3 := *cfg1 163 cfg3.PeerUrls = "127.0.0.1:\n8291" 164 _, err = cfg3.genEmbedEtcdConfig(embed.NewConfig()) 165 c.Assert(terror.ErrMasterGenEmbedEtcdConfigFail.Equal(err), check.IsTrue) 166 c.Assert(err, check.ErrorMatches, "(?m).*invalid peer-urls.*") 167 cfg3.PeerUrls = "http://172.100.8.8:8291" 168 etcdCfg, err = cfg3.genEmbedEtcdConfig(embed.NewConfig()) 169 c.Assert(err, check.IsNil) 170 c.Assert(etcdCfg.ListenPeerUrls, check.DeepEquals, []url.URL{{Scheme: "http", Host: "172.100.8.8:8291"}}) 171 172 cfg4 := *cfg1 173 cfg4.AdvertisePeerUrls = "127.0.0.1:\n8291" 174 _, err = cfg4.genEmbedEtcdConfig(embed.NewConfig()) 175 c.Assert(terror.ErrMasterGenEmbedEtcdConfigFail.Equal(err), check.IsTrue) 176 c.Assert(err, check.ErrorMatches, "(?m).*invalid advertise-peer-urls.*") 177 cfg4.AdvertisePeerUrls = "http://172.100.8.8:8291" 178 etcdCfg, err = cfg4.genEmbedEtcdConfig(embed.NewConfig()) 179 c.Assert(err, check.IsNil) 180 c.Assert(etcdCfg.AdvertisePeerUrls, check.DeepEquals, []url.URL{{Scheme: "http", Host: "172.100.8.8:8291"}}) 181 } 182 183 func (t *testConfigSuite) TestParseURLs(c *check.C) { 184 cases := []struct { 185 str string 186 urls []url.URL 187 hasErr bool 188 }{ 189 {}, // empty str 190 { 191 str: "http://127.0.0.1:8291", 192 urls: []url.URL{{Scheme: "http", Host: "127.0.0.1:8291"}}, 193 }, 194 { 195 str: "https://127.0.0.1:8291", 196 urls: []url.URL{{Scheme: "https", Host: "127.0.0.1:8291"}}, 197 }, 198 { 199 str: "http://127.0.0.1:8291,http://127.0.0.1:18291", 200 urls: []url.URL{ 201 {Scheme: "http", Host: "127.0.0.1:8291"}, 202 {Scheme: "http", Host: "127.0.0.1:18291"}, 203 }, 204 }, 205 { 206 str: "https://127.0.0.1:8291,https://127.0.0.1:18291", 207 urls: []url.URL{ 208 {Scheme: "https", Host: "127.0.0.1:8291"}, 209 {Scheme: "https", Host: "127.0.0.1:18291"}, 210 }, 211 }, 212 { 213 str: "127.0.0.1:8291", // no scheme 214 urls: []url.URL{{Scheme: "http", Host: "127.0.0.1:8291"}}, 215 }, 216 { 217 str: "http://:8291", // no IP 218 urls: []url.URL{{Scheme: "http", Host: "0.0.0.0:8291"}}, 219 }, 220 { 221 str: ":8291", // no scheme, no IP 222 urls: []url.URL{{Scheme: "http", Host: "0.0.0.0:8291"}}, 223 }, 224 { 225 str: "http://", // no IP, no port 226 urls: []url.URL{{Scheme: "http", Host: ""}}, 227 }, 228 { 229 str: "http://\n127.0.0.1:8291", // invalid char in URL 230 hasErr: true, 231 }, 232 { 233 str: ":8291,http://127.0.0.1:18291", 234 urls: []url.URL{ 235 {Scheme: "http", Host: "0.0.0.0:8291"}, 236 {Scheme: "http", Host: "127.0.0.1:18291"}, 237 }, 238 }, 239 { 240 str: "LancedeMacBook-Pro.local:8661", 241 urls: []url.URL{ 242 {Scheme: "http", Host: "LancedeMacBook-Pro.local:8661"}, 243 }, 244 }, 245 } 246 247 for _, cs := range cases { 248 c.Logf("raw string %s", cs.str) 249 urls, err := parseURLs(cs.str) 250 if cs.hasErr { 251 c.Assert(terror.ErrMasterParseURLFail.Equal(err), check.IsTrue) 252 } else { 253 c.Assert(err, check.IsNil) 254 c.Assert(urls, check.DeepEquals, cs.urls) 255 } 256 } 257 } 258 259 func (t *testConfigSuite) TestAdjustAddr(c *check.C) { 260 cfg := NewConfig() 261 c.Assert(cfg.FromContent(SampleConfig), check.IsNil) 262 c.Assert(cfg.adjust(), check.IsNil) 263 264 // invalid `advertise-addr` 265 cfg.AdvertiseAddr = "127.0.0.1" 266 c.Assert(terror.ErrMasterAdvertiseAddrNotValid.Equal(cfg.adjust()), check.IsTrue) 267 cfg.AdvertiseAddr = "0.0.0.0:8261" 268 c.Assert(terror.ErrMasterAdvertiseAddrNotValid.Equal(cfg.adjust()), check.IsTrue) 269 270 // clear `advertise-addr`, still invalid because no `host` in `master-addr`. 271 cfg.AdvertiseAddr = "" 272 c.Assert(terror.ErrMasterHostPortNotValid.Equal(cfg.adjust()), check.IsTrue) 273 274 cfg.MasterAddr = "127.0.0.1:8261" 275 c.Assert(cfg.adjust(), check.IsNil) 276 c.Assert(cfg.AdvertiseAddr, check.Equals, cfg.MasterAddr) 277 } 278 279 func (t *testConfigSuite) TestAdjustOpenAPI(c *check.C) { 280 cfg := NewConfig() 281 c.Assert(cfg.FromContent(SampleConfig), check.IsNil) 282 c.Assert(cfg.adjust(), check.IsNil) 283 284 // test default value 285 c.Assert(cfg.OpenAPI, check.Equals, false) 286 c.Assert(cfg.ExperimentalFeatures.OpenAPI, check.Equals, false) 287 288 // adjust openapi from experimental-features 289 cfg.ExperimentalFeatures.OpenAPI = true 290 c.Assert(cfg.adjust(), check.IsNil) 291 c.Assert(cfg.OpenAPI, check.Equals, true) 292 c.Assert(cfg.ExperimentalFeatures.OpenAPI, check.Equals, false) 293 294 // test from flags 295 c.Assert(cfg.Parse([]string{"--openapi=false", "--master-addr=127.0.0.1:8261"}), check.IsNil) 296 c.Assert(cfg.adjust(), check.IsNil) 297 c.Assert(cfg.OpenAPI, check.Equals, false) 298 c.Assert(cfg.Parse([]string{"--openapi=true", "--master-addr=127.0.0.1:8261"}), check.IsNil) 299 c.Assert(cfg.adjust(), check.IsNil) 300 c.Assert(cfg.OpenAPI, check.Equals, true) 301 } 302 303 func TestAdjustSecretKeyPath(t *testing.T) { 304 cfg := &Config{} 305 require.NoError(t, cfg.adjustSecretKeyPath()) 306 307 // non exist file 308 dir := t.TempDir() 309 cfg.SecretKeyPath = filepath.Join(dir, "non-exist") 310 err := cfg.adjustSecretKeyPath() 311 require.True(t, terror.ErrConfigSecretKeyPath.Equal(err)) 312 require.ErrorContains(t, err, "no such file") 313 require.Nil(t, cfg.SecretKey) 314 315 // not hex string 316 require.NoError(t, os.WriteFile(filepath.Join(dir, "secret"), []byte("secret"), 0o644)) 317 cfg.SecretKeyPath = filepath.Join(dir, "secret") 318 err = cfg.adjustSecretKeyPath() 319 require.True(t, terror.ErrConfigSecretKeyPath.Equal(err)) 320 require.ErrorContains(t, err, "encoding/hex: invalid byte") 321 require.Nil(t, cfg.SecretKey) 322 323 // not enough length 324 require.NoError(t, os.WriteFile(filepath.Join(dir, "secret-2"), []byte("2334"), 0o644)) 325 cfg.SecretKeyPath = filepath.Join(dir, "secret-2") 326 err = cfg.adjustSecretKeyPath() 327 require.True(t, terror.ErrConfigSecretKeyPath.Equal(err)) 328 require.ErrorContains(t, err, "hex AES-256 key of length 64") 329 require.Nil(t, cfg.SecretKey) 330 331 // works 332 key := make([]byte, 32) 333 _, err = rand.Read(key) 334 require.NoError(t, err) 335 require.NoError(t, os.WriteFile(filepath.Join(dir, "secret-3"), []byte(" \t"+hex.EncodeToString(key)+"\r\n \n"), 0o644)) 336 cfg.SecretKeyPath = filepath.Join(dir, "secret-3") 337 require.NoError(t, cfg.adjustSecretKeyPath()) 338 require.Equal(t, key, cfg.SecretKey) 339 }