config_test.go 13 KB

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