config_test.go 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573
  1. // Copyright 2015 The etcd Authors
  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. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. package etcdmain
  15. import (
  16. "fmt"
  17. "io/ioutil"
  18. "net/url"
  19. "os"
  20. "reflect"
  21. "strings"
  22. "testing"
  23. "github.com/coreos/etcd/embed"
  24. "github.com/ghodss/yaml"
  25. )
  26. func TestConfigParsingMemberFlags(t *testing.T) {
  27. args := []string{
  28. "-data-dir=testdir",
  29. "-name=testname",
  30. "-max-wals=10",
  31. "-max-snapshots=10",
  32. "-snapshot-count=10",
  33. "-listen-peer-urls=http://localhost:8000,https://localhost:8001",
  34. "-listen-client-urls=http://localhost:7000,https://localhost:7001",
  35. // it should be set if -listen-client-urls is set
  36. "-advertise-client-urls=http://localhost:7000,https://localhost:7001",
  37. }
  38. cfg := newConfig()
  39. err := cfg.parse(args)
  40. if err != nil {
  41. t.Fatal(err)
  42. }
  43. validateMemberFlags(t, cfg)
  44. }
  45. func TestConfigFileMemberFields(t *testing.T) {
  46. yc := struct {
  47. Dir string `json:"data-dir"`
  48. MaxSnapFiles uint `json:"max-snapshots"`
  49. MaxWalFiles uint `json:"max-wals"`
  50. Name string `json:"name"`
  51. SnapCount uint64 `json:"snapshot-count"`
  52. LPUrls string `json:"listen-peer-urls"`
  53. LCUrls string `json:"listen-client-urls"`
  54. AcurlsCfgFile string `json:"advertise-client-urls"`
  55. }{
  56. "testdir",
  57. 10,
  58. 10,
  59. "testname",
  60. 10,
  61. "http://localhost:8000,https://localhost:8001",
  62. "http://localhost:7000,https://localhost:7001",
  63. "http://localhost:7000,https://localhost:7001",
  64. }
  65. b, err := yaml.Marshal(&yc)
  66. if err != nil {
  67. t.Fatal(err)
  68. }
  69. tmpfile := mustCreateCfgFile(t, b)
  70. defer os.Remove(tmpfile.Name())
  71. args := []string{fmt.Sprintf("--config-file=%s", tmpfile.Name())}
  72. cfg := newConfig()
  73. if err = cfg.parse(args); err != nil {
  74. t.Fatal(err)
  75. }
  76. validateMemberFlags(t, cfg)
  77. }
  78. func TestConfigParsingClusteringFlags(t *testing.T) {
  79. args := []string{
  80. "-initial-cluster=0=http://localhost:8000",
  81. "-initial-cluster-state=existing",
  82. "-initial-cluster-token=etcdtest",
  83. "-initial-advertise-peer-urls=http://localhost:8000,https://localhost:8001",
  84. "-advertise-client-urls=http://localhost:7000,https://localhost:7001",
  85. "-discovery-fallback=exit",
  86. }
  87. cfg := newConfig()
  88. if err := cfg.parse(args); err != nil {
  89. t.Fatal(err)
  90. }
  91. validateClusteringFlags(t, cfg)
  92. }
  93. func TestConfigFileClusteringFields(t *testing.T) {
  94. yc := struct {
  95. InitialCluster string `json:"initial-cluster"`
  96. ClusterState string `json:"initial-cluster-state"`
  97. InitialClusterToken string `json:"initial-cluster-token"`
  98. Apurls string `json:"initial-advertise-peer-urls"`
  99. Acurls string `json:"advertise-client-urls"`
  100. Fallback string `json:"discovery-fallback"`
  101. }{
  102. "0=http://localhost:8000",
  103. "existing",
  104. "etcdtest",
  105. "http://localhost:8000,https://localhost:8001",
  106. "http://localhost:7000,https://localhost:7001",
  107. "exit",
  108. }
  109. b, err := yaml.Marshal(&yc)
  110. if err != nil {
  111. t.Fatal(err)
  112. }
  113. tmpfile := mustCreateCfgFile(t, b)
  114. defer os.Remove(tmpfile.Name())
  115. args := []string{fmt.Sprintf("--config-file=%s", tmpfile.Name())}
  116. cfg := newConfig()
  117. err = cfg.parse(args)
  118. if err != nil {
  119. t.Fatal(err)
  120. }
  121. validateClusteringFlags(t, cfg)
  122. }
  123. func TestConfigFileClusteringFlags(t *testing.T) {
  124. tests := []struct {
  125. Name string `json:"name"`
  126. InitialCluster string `json:"initial-cluster"`
  127. DNSCluster string `json:"discovery-srv"`
  128. Durl string `json:"discovery"`
  129. }{
  130. {
  131. // Use default name and generate a default initial-cluster
  132. },
  133. {
  134. Name: "non-default",
  135. },
  136. {
  137. InitialCluster: "0=localhost:8000",
  138. },
  139. {
  140. Name: "non-default",
  141. InitialCluster: "0=localhost:8000",
  142. },
  143. {
  144. DNSCluster: "example.com",
  145. },
  146. {
  147. Name: "non-default",
  148. DNSCluster: "example.com",
  149. },
  150. {
  151. Durl: "http://example.com/abc",
  152. },
  153. {
  154. Name: "non-default",
  155. Durl: "http://example.com/abc",
  156. },
  157. }
  158. for i, tt := range tests {
  159. b, err := yaml.Marshal(&tt)
  160. if err != nil {
  161. t.Fatal(err)
  162. }
  163. tmpfile := mustCreateCfgFile(t, b)
  164. defer os.Remove(tmpfile.Name())
  165. args := []string{fmt.Sprintf("--config-file=%s", tmpfile.Name())}
  166. cfg := newConfig()
  167. if err := cfg.parse(args); err != nil {
  168. t.Errorf("%d: err = %v", i, err)
  169. }
  170. }
  171. }
  172. func TestConfigParsingOtherFlags(t *testing.T) {
  173. args := []string{"-proxy=readonly"}
  174. cfg := newConfig()
  175. err := cfg.parse(args)
  176. if err != nil {
  177. t.Fatal(err)
  178. }
  179. validateOtherFlags(t, cfg)
  180. }
  181. func TestConfigFileOtherFields(t *testing.T) {
  182. yc := struct {
  183. ProxyCfgFile string `json:"proxy"`
  184. }{
  185. "readonly",
  186. }
  187. b, err := yaml.Marshal(&yc)
  188. if err != nil {
  189. t.Fatal(err)
  190. }
  191. tmpfile := mustCreateCfgFile(t, b)
  192. defer os.Remove(tmpfile.Name())
  193. args := []string{fmt.Sprintf("--config-file=%s", tmpfile.Name())}
  194. cfg := newConfig()
  195. err = cfg.parse(args)
  196. if err != nil {
  197. t.Fatal(err)
  198. }
  199. validateOtherFlags(t, cfg)
  200. }
  201. func TestConfigParsingConflictClusteringFlags(t *testing.T) {
  202. conflictArgs := [][]string{
  203. {
  204. "-initial-cluster=0=localhost:8000",
  205. "-discovery=http://example.com/abc",
  206. },
  207. {
  208. "-discovery-srv=example.com",
  209. "-discovery=http://example.com/abc",
  210. },
  211. {
  212. "-initial-cluster=0=localhost:8000",
  213. "-discovery-srv=example.com",
  214. },
  215. {
  216. "-initial-cluster=0=localhost:8000",
  217. "-discovery=http://example.com/abc",
  218. "-discovery-srv=example.com",
  219. },
  220. }
  221. for i, tt := range conflictArgs {
  222. cfg := newConfig()
  223. if err := cfg.parse(tt); err != embed.ErrConflictBootstrapFlags {
  224. t.Errorf("%d: err = %v, want %v", i, err, embed.ErrConflictBootstrapFlags)
  225. }
  226. }
  227. }
  228. func TestConfigFileConflictClusteringFlags(t *testing.T) {
  229. tests := []struct {
  230. InitialCluster string `json:"initial-cluster"`
  231. DNSCluster string `json:"discovery-srv"`
  232. Durl string `json:"discovery"`
  233. }{
  234. {
  235. InitialCluster: "0=localhost:8000",
  236. Durl: "http://example.com/abc",
  237. },
  238. {
  239. DNSCluster: "example.com",
  240. Durl: "http://example.com/abc",
  241. },
  242. {
  243. InitialCluster: "0=localhost:8000",
  244. DNSCluster: "example.com",
  245. },
  246. {
  247. InitialCluster: "0=localhost:8000",
  248. Durl: "http://example.com/abc",
  249. DNSCluster: "example.com",
  250. },
  251. }
  252. for i, tt := range tests {
  253. b, err := yaml.Marshal(&tt)
  254. if err != nil {
  255. t.Fatal(err)
  256. }
  257. tmpfile := mustCreateCfgFile(t, b)
  258. defer os.Remove(tmpfile.Name())
  259. args := []string{fmt.Sprintf("--config-file=%s", tmpfile.Name())}
  260. cfg := newConfig()
  261. if err := cfg.parse(args); err != embed.ErrConflictBootstrapFlags {
  262. t.Errorf("%d: err = %v, want %v", i, err, embed.ErrConflictBootstrapFlags)
  263. }
  264. }
  265. }
  266. func TestConfigParsingMissedAdvertiseClientURLsFlag(t *testing.T) {
  267. tests := []struct {
  268. args []string
  269. werr error
  270. }{
  271. {
  272. []string{
  273. "-initial-cluster=infra1=http://127.0.0.1:2380",
  274. "-listen-client-urls=http://127.0.0.1:2379",
  275. },
  276. embed.ErrUnsetAdvertiseClientURLsFlag,
  277. },
  278. {
  279. []string{
  280. "-discovery-srv=example.com",
  281. "-listen-client-urls=http://127.0.0.1:2379",
  282. },
  283. embed.ErrUnsetAdvertiseClientURLsFlag,
  284. },
  285. {
  286. []string{
  287. "-discovery=http://example.com/abc",
  288. "-discovery-fallback=exit",
  289. "-listen-client-urls=http://127.0.0.1:2379",
  290. },
  291. embed.ErrUnsetAdvertiseClientURLsFlag,
  292. },
  293. {
  294. []string{
  295. "-listen-client-urls=http://127.0.0.1:2379",
  296. },
  297. embed.ErrUnsetAdvertiseClientURLsFlag,
  298. },
  299. {
  300. []string{
  301. "-discovery=http://example.com/abc",
  302. "-listen-client-urls=http://127.0.0.1:2379",
  303. },
  304. nil,
  305. },
  306. {
  307. []string{
  308. "-proxy=on",
  309. "-listen-client-urls=http://127.0.0.1:2379",
  310. },
  311. nil,
  312. },
  313. {
  314. []string{
  315. "-proxy=readonly",
  316. "-listen-client-urls=http://127.0.0.1:2379",
  317. },
  318. nil,
  319. },
  320. }
  321. for i, tt := range tests {
  322. cfg := newConfig()
  323. if err := cfg.parse(tt.args); err != tt.werr {
  324. t.Errorf("%d: err = %v, want %v", i, err, tt.werr)
  325. }
  326. }
  327. }
  328. func TestConfigIsNewCluster(t *testing.T) {
  329. tests := []struct {
  330. state string
  331. wIsNew bool
  332. }{
  333. {embed.ClusterStateFlagExisting, false},
  334. {embed.ClusterStateFlagNew, true},
  335. }
  336. for i, tt := range tests {
  337. cfg := newConfig()
  338. args := []string{"--initial-cluster-state", tests[i].state}
  339. if err := cfg.parse(args); err != nil {
  340. t.Fatalf("#%d: unexpected clusterState.Set error: %v", i, err)
  341. }
  342. if g := cfg.IsNewCluster(); g != tt.wIsNew {
  343. t.Errorf("#%d: isNewCluster = %v, want %v", i, g, tt.wIsNew)
  344. }
  345. }
  346. }
  347. func TestConfigIsProxy(t *testing.T) {
  348. tests := []struct {
  349. proxy string
  350. wIsProxy bool
  351. }{
  352. {proxyFlagOff, false},
  353. {proxyFlagReadonly, true},
  354. {proxyFlagOn, true},
  355. }
  356. for i, tt := range tests {
  357. cfg := newConfig()
  358. if err := cfg.proxy.Set(tt.proxy); err != nil {
  359. t.Fatalf("#%d: unexpected proxy.Set error: %v", i, err)
  360. }
  361. if g := cfg.isProxy(); g != tt.wIsProxy {
  362. t.Errorf("#%d: isProxy = %v, want %v", i, g, tt.wIsProxy)
  363. }
  364. }
  365. }
  366. func TestConfigIsReadonlyProxy(t *testing.T) {
  367. tests := []struct {
  368. proxy string
  369. wIsReadonly bool
  370. }{
  371. {proxyFlagOff, false},
  372. {proxyFlagReadonly, true},
  373. {proxyFlagOn, false},
  374. }
  375. for i, tt := range tests {
  376. cfg := newConfig()
  377. if err := cfg.proxy.Set(tt.proxy); err != nil {
  378. t.Fatalf("#%d: unexpected proxy.Set error: %v", i, err)
  379. }
  380. if g := cfg.isReadonlyProxy(); g != tt.wIsReadonly {
  381. t.Errorf("#%d: isReadonlyProxy = %v, want %v", i, g, tt.wIsReadonly)
  382. }
  383. }
  384. }
  385. func TestConfigShouldFallbackToProxy(t *testing.T) {
  386. tests := []struct {
  387. fallback string
  388. wFallback bool
  389. }{
  390. {fallbackFlagProxy, true},
  391. {fallbackFlagExit, false},
  392. }
  393. for i, tt := range tests {
  394. cfg := newConfig()
  395. if err := cfg.fallback.Set(tt.fallback); err != nil {
  396. t.Fatalf("#%d: unexpected fallback.Set error: %v", i, err)
  397. }
  398. if g := cfg.shouldFallbackToProxy(); g != tt.wFallback {
  399. t.Errorf("#%d: shouldFallbackToProxy = %v, want %v", i, g, tt.wFallback)
  400. }
  401. }
  402. }
  403. func TestConfigFileElectionTimeout(t *testing.T) {
  404. tests := []struct {
  405. TickMs uint `json:"heartbeat-interval"`
  406. ElectionMs uint `json:"election-timeout"`
  407. errStr string
  408. }{
  409. {
  410. ElectionMs: 1000,
  411. TickMs: 800,
  412. errStr: "should be at least as 5 times as",
  413. },
  414. {
  415. ElectionMs: 60000,
  416. errStr: "is too long, and should be set less than",
  417. },
  418. }
  419. for i, tt := range tests {
  420. b, err := yaml.Marshal(&tt)
  421. if err != nil {
  422. t.Fatal(err)
  423. }
  424. tmpfile := mustCreateCfgFile(t, b)
  425. defer os.Remove(tmpfile.Name())
  426. args := []string{fmt.Sprintf("--config-file=%s", tmpfile.Name())}
  427. cfg := newConfig()
  428. if err := cfg.parse(args); err == nil || !strings.Contains(err.Error(), tt.errStr) {
  429. t.Errorf("%d: Wrong err = %v", i, err)
  430. }
  431. }
  432. }
  433. func mustCreateCfgFile(t *testing.T, b []byte) *os.File {
  434. tmpfile, err := ioutil.TempFile("", "servercfg")
  435. if err != nil {
  436. t.Fatal(err)
  437. }
  438. _, err = tmpfile.Write(b)
  439. if err != nil {
  440. t.Fatal(err)
  441. }
  442. err = tmpfile.Close()
  443. if err != nil {
  444. t.Fatal(err)
  445. }
  446. return tmpfile
  447. }
  448. func validateMemberFlags(t *testing.T, cfg *config) {
  449. wcfg := &embed.Config{
  450. Dir: "testdir",
  451. LPUrls: []url.URL{{Scheme: "http", Host: "localhost:8000"}, {Scheme: "https", Host: "localhost:8001"}},
  452. LCUrls: []url.URL{{Scheme: "http", Host: "localhost:7000"}, {Scheme: "https", Host: "localhost:7001"}},
  453. MaxSnapFiles: 10,
  454. MaxWalFiles: 10,
  455. Name: "testname",
  456. SnapCount: 10,
  457. }
  458. if cfg.Dir != wcfg.Dir {
  459. t.Errorf("dir = %v, want %v", cfg.Dir, wcfg.Dir)
  460. }
  461. if cfg.MaxSnapFiles != wcfg.MaxSnapFiles {
  462. t.Errorf("maxsnap = %v, want %v", cfg.MaxSnapFiles, wcfg.MaxSnapFiles)
  463. }
  464. if cfg.MaxWalFiles != wcfg.MaxWalFiles {
  465. t.Errorf("maxwal = %v, want %v", cfg.MaxWalFiles, wcfg.MaxWalFiles)
  466. }
  467. if cfg.Name != wcfg.Name {
  468. t.Errorf("name = %v, want %v", cfg.Name, wcfg.Name)
  469. }
  470. if cfg.SnapCount != wcfg.SnapCount {
  471. t.Errorf("snapcount = %v, want %v", cfg.SnapCount, wcfg.SnapCount)
  472. }
  473. if !reflect.DeepEqual(cfg.LPUrls, wcfg.LPUrls) {
  474. t.Errorf("listen-peer-urls = %v, want %v", cfg.LPUrls, wcfg.LPUrls)
  475. }
  476. if !reflect.DeepEqual(cfg.LCUrls, wcfg.LCUrls) {
  477. t.Errorf("listen-client-urls = %v, want %v", cfg.LCUrls, wcfg.LCUrls)
  478. }
  479. }
  480. func validateClusteringFlags(t *testing.T, cfg *config) {
  481. wcfg := newConfig()
  482. wcfg.APUrls = []url.URL{{Scheme: "http", Host: "localhost:8000"}, {Scheme: "https", Host: "localhost:8001"}}
  483. wcfg.ACUrls = []url.URL{{Scheme: "http", Host: "localhost:7000"}, {Scheme: "https", Host: "localhost:7001"}}
  484. wcfg.ClusterState = embed.ClusterStateFlagExisting
  485. wcfg.fallback.Set(fallbackFlagExit)
  486. wcfg.InitialCluster = "0=http://localhost:8000"
  487. wcfg.InitialClusterToken = "etcdtest"
  488. if cfg.ClusterState != wcfg.ClusterState {
  489. t.Errorf("clusterState = %v, want %v", cfg.ClusterState, wcfg.ClusterState)
  490. }
  491. if cfg.fallback.String() != wcfg.fallback.String() {
  492. t.Errorf("fallback = %v, want %v", cfg.fallback, wcfg.fallback)
  493. }
  494. if cfg.InitialCluster != wcfg.InitialCluster {
  495. t.Errorf("initialCluster = %v, want %v", cfg.InitialCluster, wcfg.InitialCluster)
  496. }
  497. if cfg.InitialClusterToken != wcfg.InitialClusterToken {
  498. t.Errorf("initialClusterToken = %v, want %v", cfg.InitialClusterToken, wcfg.InitialClusterToken)
  499. }
  500. if !reflect.DeepEqual(cfg.APUrls, wcfg.APUrls) {
  501. t.Errorf("initial-advertise-peer-urls = %v, want %v", cfg.LPUrls, wcfg.LPUrls)
  502. }
  503. if !reflect.DeepEqual(cfg.ACUrls, wcfg.ACUrls) {
  504. t.Errorf("advertise-client-urls = %v, want %v", cfg.LCUrls, wcfg.LCUrls)
  505. }
  506. }
  507. func validateOtherFlags(t *testing.T, cfg *config) {
  508. wcfg := newConfig()
  509. wcfg.proxy.Set(proxyFlagReadonly)
  510. if cfg.proxy.String() != wcfg.proxy.String() {
  511. t.Errorf("proxy = %v, want %v", cfg.proxy, wcfg.proxy)
  512. }
  513. }