1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 package etcdmain
16
17 import (
18 "flag"
19 "fmt"
20 "io/ioutil"
21 "net/url"
22 "os"
23 "reflect"
24 "strings"
25 "testing"
26
27 "go.etcd.io/etcd/pkg/v3/flags"
28 "go.etcd.io/etcd/server/v3/embed"
29 "sigs.k8s.io/yaml"
30 )
31
32 func TestConfigParsingMemberFlags(t *testing.T) {
33 args := []string{
34 "-data-dir=testdir",
35 "-name=testname",
36 "-max-wals=10",
37 "-max-snapshots=10",
38 "-snapshot-count=10",
39 "-listen-peer-urls=http://localhost:8000,https://localhost:8001",
40 "-listen-client-urls=http://localhost:7000,https://localhost:7001",
41 "-listen-client-http-urls=http://localhost:7002,https://localhost:7003",
42
43 "-advertise-client-urls=http://localhost:7000,https://localhost:7001",
44 }
45
46 cfg := newConfig()
47 err := cfg.parse(args)
48 if err != nil {
49 t.Fatal(err)
50 }
51
52 validateMemberFlags(t, cfg)
53 }
54
55 func TestConfigFileMemberFields(t *testing.T) {
56 yc := struct {
57 Dir string `json:"data-dir"`
58 MaxSnapFiles uint `json:"max-snapshots"`
59 MaxWalFiles uint `json:"max-wals"`
60 Name string `json:"name"`
61 SnapshotCount uint64 `json:"snapshot-count"`
62 ListenPeerUrls string `json:"listen-peer-urls"`
63 ListenClientUrls string `json:"listen-client-urls"`
64 ListenClientHttpUrls string `json:"listen-client-http-urls"`
65 AdvertiseClientUrls string `json:"advertise-client-urls"`
66 }{
67 "testdir",
68 10,
69 10,
70 "testname",
71 10,
72 "http://localhost:8000,https://localhost:8001",
73 "http://localhost:7000,https://localhost:7001",
74 "http://localhost:7002,https://localhost:7003",
75 "http://localhost:7000,https://localhost:7001",
76 }
77
78 b, err := yaml.Marshal(&yc)
79 if err != nil {
80 t.Fatal(err)
81 }
82
83 tmpfile := mustCreateCfgFile(t, b)
84 defer os.Remove(tmpfile.Name())
85
86 args := []string{fmt.Sprintf("--config-file=%s", tmpfile.Name())}
87
88 cfg := newConfig()
89 if err = cfg.parse(args); err != nil {
90 t.Fatal(err)
91 }
92
93 validateMemberFlags(t, cfg)
94 }
95
96 func TestConfigParsingClusteringFlags(t *testing.T) {
97 args := []string{
98 "-initial-cluster=0=http://localhost:8000",
99 "-initial-cluster-state=existing",
100 "-initial-cluster-token=etcdtest",
101 "-initial-advertise-peer-urls=http://localhost:8000,https://localhost:8001",
102 "-advertise-client-urls=http://localhost:7000,https://localhost:7001",
103 "-discovery-fallback=exit",
104 }
105
106 cfg := newConfig()
107 if err := cfg.parse(args); err != nil {
108 t.Fatal(err)
109 }
110
111 validateClusteringFlags(t, cfg)
112 }
113
114 func TestConfigFileClusteringFields(t *testing.T) {
115 yc := struct {
116 InitialCluster string `json:"initial-cluster"`
117 ClusterState string `json:"initial-cluster-state"`
118 InitialClusterToken string `json:"initial-cluster-token"`
119 Apurls string `json:"initial-advertise-peer-urls"`
120 Acurls string `json:"advertise-client-urls"`
121 Fallback string `json:"discovery-fallback"`
122 }{
123 "0=http://localhost:8000",
124 "existing",
125 "etcdtest",
126 "http://localhost:8000,https://localhost:8001",
127 "http://localhost:7000,https://localhost:7001",
128 "exit",
129 }
130
131 b, err := yaml.Marshal(&yc)
132 if err != nil {
133 t.Fatal(err)
134 }
135
136 tmpfile := mustCreateCfgFile(t, b)
137 defer os.Remove(tmpfile.Name())
138
139 args := []string{fmt.Sprintf("--config-file=%s", tmpfile.Name())}
140 cfg := newConfig()
141 err = cfg.parse(args)
142 if err != nil {
143 t.Fatal(err)
144 }
145
146 validateClusteringFlags(t, cfg)
147 }
148
149 func TestConfigFileClusteringFlags(t *testing.T) {
150 tests := []struct {
151 Name string `json:"name"`
152 InitialCluster string `json:"initial-cluster"`
153 DNSCluster string `json:"discovery-srv"`
154 Durl string `json:"discovery"`
155 }{
156
157 {},
158 {
159 Name: "non-default",
160 },
161 {
162 InitialCluster: "0=localhost:8000",
163 },
164 {
165 Name: "non-default",
166 InitialCluster: "0=localhost:8000",
167 },
168 {
169 DNSCluster: "example.com",
170 },
171 {
172 Name: "non-default",
173 DNSCluster: "example.com",
174 },
175 {
176 Durl: "http://example.com/abc",
177 },
178 {
179 Name: "non-default",
180 Durl: "http://example.com/abc",
181 },
182 }
183
184 for i, tt := range tests {
185 b, err := yaml.Marshal(&tt)
186 if err != nil {
187 t.Fatal(err)
188 }
189
190 tmpfile := mustCreateCfgFile(t, b)
191 defer os.Remove(tmpfile.Name())
192
193 args := []string{fmt.Sprintf("--config-file=%s", tmpfile.Name())}
194
195 cfg := newConfig()
196 if err := cfg.parse(args); err != nil {
197 t.Errorf("%d: err = %v", i, err)
198 }
199 }
200 }
201
202 func TestConfigParsingOtherFlags(t *testing.T) {
203 args := []string{"-proxy=readonly"}
204
205 cfg := newConfig()
206 err := cfg.parse(args)
207 if err != nil {
208 t.Fatal(err)
209 }
210
211 validateOtherFlags(t, cfg)
212 }
213
214 func TestConfigFileOtherFields(t *testing.T) {
215 yc := struct {
216 ProxyCfgFile string `json:"proxy"`
217 }{
218 "readonly",
219 }
220
221 b, err := yaml.Marshal(&yc)
222 if err != nil {
223 t.Fatal(err)
224 }
225
226 tmpfile := mustCreateCfgFile(t, b)
227 defer os.Remove(tmpfile.Name())
228
229 args := []string{fmt.Sprintf("--config-file=%s", tmpfile.Name())}
230
231 cfg := newConfig()
232 err = cfg.parse(args)
233 if err != nil {
234 t.Fatal(err)
235 }
236
237 validateOtherFlags(t, cfg)
238 }
239
240 func TestConfigParsingConflictClusteringFlags(t *testing.T) {
241 conflictArgs := [][]string{
242 {
243 "-initial-cluster=0=localhost:8000",
244 "-discovery=http://example.com/abc",
245 },
246 {
247 "-discovery-srv=example.com",
248 "-discovery=http://example.com/abc",
249 },
250 {
251 "-initial-cluster=0=localhost:8000",
252 "-discovery-srv=example.com",
253 },
254 {
255 "-initial-cluster=0=localhost:8000",
256 "-discovery=http://example.com/abc",
257 "-discovery-srv=example.com",
258 },
259 }
260
261 for i, tt := range conflictArgs {
262 cfg := newConfig()
263 if err := cfg.parse(tt); err != embed.ErrConflictBootstrapFlags {
264 t.Errorf("%d: err = %v, want %v", i, err, embed.ErrConflictBootstrapFlags)
265 }
266 }
267 }
268
269 func TestConfigFileConflictClusteringFlags(t *testing.T) {
270 tests := []struct {
271 InitialCluster string `json:"initial-cluster"`
272 DNSCluster string `json:"discovery-srv"`
273 Durl string `json:"discovery"`
274 }{
275 {
276 InitialCluster: "0=localhost:8000",
277 Durl: "http://example.com/abc",
278 },
279 {
280 DNSCluster: "example.com",
281 Durl: "http://example.com/abc",
282 },
283 {
284 InitialCluster: "0=localhost:8000",
285 DNSCluster: "example.com",
286 },
287 {
288 InitialCluster: "0=localhost:8000",
289 Durl: "http://example.com/abc",
290 DNSCluster: "example.com",
291 },
292 }
293
294 for i, tt := range tests {
295 b, err := yaml.Marshal(&tt)
296 if err != nil {
297 t.Fatal(err)
298 }
299
300 tmpfile := mustCreateCfgFile(t, b)
301 defer os.Remove(tmpfile.Name())
302
303 args := []string{fmt.Sprintf("--config-file=%s", tmpfile.Name())}
304
305 cfg := newConfig()
306 if err := cfg.parse(args); err != embed.ErrConflictBootstrapFlags {
307 t.Errorf("%d: err = %v, want %v", i, err, embed.ErrConflictBootstrapFlags)
308 }
309 }
310 }
311
312 func TestConfigParsingMissedAdvertiseClientURLsFlag(t *testing.T) {
313 tests := []struct {
314 args []string
315 werr error
316 }{
317 {
318 []string{
319 "-initial-cluster=infra1=http://127.0.0.1:2380",
320 "-listen-client-urls=http://127.0.0.1:2379",
321 },
322 embed.ErrUnsetAdvertiseClientURLsFlag,
323 },
324 {
325 []string{
326 "-discovery-srv=example.com",
327 "-listen-client-urls=http://127.0.0.1:2379",
328 },
329 embed.ErrUnsetAdvertiseClientURLsFlag,
330 },
331 {
332 []string{
333 "-discovery=http://example.com/abc",
334 "-discovery-fallback=exit",
335 "-listen-client-urls=http://127.0.0.1:2379",
336 },
337 embed.ErrUnsetAdvertiseClientURLsFlag,
338 },
339 {
340 []string{
341 "-listen-client-urls=http://127.0.0.1:2379",
342 },
343 embed.ErrUnsetAdvertiseClientURLsFlag,
344 },
345 {
346 []string{
347 "-discovery=http://example.com/abc",
348 "-listen-client-urls=http://127.0.0.1:2379",
349 },
350 nil,
351 },
352 {
353 []string{
354 "-proxy=on",
355 "-listen-client-urls=http://127.0.0.1:2379",
356 },
357 nil,
358 },
359 {
360 []string{
361 "-proxy=readonly",
362 "-listen-client-urls=http://127.0.0.1:2379",
363 },
364 nil,
365 },
366 }
367
368 for i, tt := range tests {
369 cfg := newConfig()
370 if err := cfg.parse(tt.args); err != tt.werr {
371 t.Errorf("%d: err = %v, want %v", i, err, tt.werr)
372 }
373 }
374 }
375
376 func TestConfigIsNewCluster(t *testing.T) {
377 tests := []struct {
378 state string
379 wIsNew bool
380 }{
381 {embed.ClusterStateFlagExisting, false},
382 {embed.ClusterStateFlagNew, true},
383 }
384 for i, tt := range tests {
385 cfg := newConfig()
386 args := []string{"--initial-cluster-state", tests[i].state}
387 if err := cfg.parse(args); err != nil {
388 t.Fatalf("#%d: unexpected clusterState.Set error: %v", i, err)
389 }
390 if g := cfg.ec.IsNewCluster(); g != tt.wIsNew {
391 t.Errorf("#%d: isNewCluster = %v, want %v", i, g, tt.wIsNew)
392 }
393 }
394 }
395
396 func TestConfigIsProxy(t *testing.T) {
397 tests := []struct {
398 proxy string
399 wIsProxy bool
400 }{
401 {proxyFlagOff, false},
402 {proxyFlagReadonly, true},
403 {proxyFlagOn, true},
404 }
405 for i, tt := range tests {
406 cfg := newConfig()
407 if err := cfg.cf.proxy.Set(tt.proxy); err != nil {
408 t.Fatalf("#%d: unexpected proxy.Set error: %v", i, err)
409 }
410 if g := cfg.isProxy(); g != tt.wIsProxy {
411 t.Errorf("#%d: isProxy = %v, want %v", i, g, tt.wIsProxy)
412 }
413 }
414 }
415
416 func TestConfigIsReadonlyProxy(t *testing.T) {
417 tests := []struct {
418 proxy string
419 wIsReadonly bool
420 }{
421 {proxyFlagOff, false},
422 {proxyFlagReadonly, true},
423 {proxyFlagOn, false},
424 }
425 for i, tt := range tests {
426 cfg := newConfig()
427 if err := cfg.cf.proxy.Set(tt.proxy); err != nil {
428 t.Fatalf("#%d: unexpected proxy.Set error: %v", i, err)
429 }
430 if g := cfg.isReadonlyProxy(); g != tt.wIsReadonly {
431 t.Errorf("#%d: isReadonlyProxy = %v, want %v", i, g, tt.wIsReadonly)
432 }
433 }
434 }
435
436 func TestConfigShouldFallbackToProxy(t *testing.T) {
437 tests := []struct {
438 fallback string
439 wFallback bool
440 }{
441 {fallbackFlagProxy, true},
442 {fallbackFlagExit, false},
443 }
444 for i, tt := range tests {
445 cfg := newConfig()
446 if err := cfg.cf.fallback.Set(tt.fallback); err != nil {
447 t.Fatalf("#%d: unexpected fallback.Set error: %v", i, err)
448 }
449 if g := cfg.shouldFallbackToProxy(); g != tt.wFallback {
450 t.Errorf("#%d: shouldFallbackToProxy = %v, want %v", i, g, tt.wFallback)
451 }
452 }
453 }
454
455 func TestConfigFileElectionTimeout(t *testing.T) {
456 tests := []struct {
457 TickMs uint `json:"heartbeat-interval"`
458 ElectionMs uint `json:"election-timeout"`
459 errStr string
460 }{
461 {
462 ElectionMs: 1000,
463 TickMs: 800,
464 errStr: "should be at least as 5 times as",
465 },
466 {
467 ElectionMs: 60000,
468 TickMs: 10000,
469 errStr: "is too long, and should be set less than",
470 },
471 {
472 ElectionMs: 100,
473 TickMs: 0,
474 errStr: "--heartbeat-interval must be >0 (set to 0ms)",
475 },
476 {
477 ElectionMs: 0,
478 TickMs: 100,
479 errStr: "--election-timeout must be >0 (set to 0ms)",
480 },
481 }
482
483 for i, tt := range tests {
484 b, err := yaml.Marshal(&tt)
485 if err != nil {
486 t.Fatal(err)
487 }
488
489 tmpfile := mustCreateCfgFile(t, b)
490 defer os.Remove(tmpfile.Name())
491
492 args := []string{fmt.Sprintf("--config-file=%s", tmpfile.Name())}
493
494 cfg := newConfig()
495 if err := cfg.parse(args); err == nil || !strings.Contains(err.Error(), tt.errStr) {
496 t.Errorf("%d: Wrong err = %v", i, err)
497 }
498 }
499 }
500
501 func TestFlagsPresentInHelp(t *testing.T) {
502 cfg := newConfig()
503 cfg.cf.flagSet.VisitAll(func(f *flag.Flag) {
504 if _, ok := f.Value.(*flags.IgnoredFlag); ok {
505
506 return
507 }
508
509 flagText := fmt.Sprintf("--%s", f.Name)
510 if !strings.Contains(flagsline, flagText) && !strings.Contains(usageline, flagText) {
511 t.Errorf("Neither flagsline nor usageline in help.go contains flag named %s", flagText)
512 }
513 })
514 }
515
516 func mustCreateCfgFile(t *testing.T, b []byte) *os.File {
517 tmpfile, err := ioutil.TempFile("", "servercfg")
518 if err != nil {
519 t.Fatal(err)
520 }
521
522 _, err = tmpfile.Write(b)
523 if err != nil {
524 t.Fatal(err)
525 }
526 err = tmpfile.Close()
527 if err != nil {
528 t.Fatal(err)
529 }
530
531 return tmpfile
532 }
533
534 func validateMemberFlags(t *testing.T, cfg *config) {
535 wcfg := &embed.Config{
536 Dir: "testdir",
537 ListenPeerUrls: []url.URL{{Scheme: "http", Host: "localhost:8000"}, {Scheme: "https", Host: "localhost:8001"}},
538 ListenClientUrls: []url.URL{{Scheme: "http", Host: "localhost:7000"}, {Scheme: "https", Host: "localhost:7001"}},
539 ListenClientHttpUrls: []url.URL{{Scheme: "http", Host: "localhost:7002"}, {Scheme: "https", Host: "localhost:7003"}},
540 MaxSnapFiles: 10,
541 MaxWalFiles: 10,
542 Name: "testname",
543 SnapshotCount: 10,
544 }
545
546 if cfg.ec.Dir != wcfg.Dir {
547 t.Errorf("dir = %v, want %v", cfg.ec.Dir, wcfg.Dir)
548 }
549 if cfg.ec.MaxSnapFiles != wcfg.MaxSnapFiles {
550 t.Errorf("maxsnap = %v, want %v", cfg.ec.MaxSnapFiles, wcfg.MaxSnapFiles)
551 }
552 if cfg.ec.MaxWalFiles != wcfg.MaxWalFiles {
553 t.Errorf("maxwal = %v, want %v", cfg.ec.MaxWalFiles, wcfg.MaxWalFiles)
554 }
555 if cfg.ec.Name != wcfg.Name {
556 t.Errorf("name = %v, want %v", cfg.ec.Name, wcfg.Name)
557 }
558 if cfg.ec.SnapshotCount != wcfg.SnapshotCount {
559 t.Errorf("snapcount = %v, want %v", cfg.ec.SnapshotCount, wcfg.SnapshotCount)
560 }
561 if !reflect.DeepEqual(cfg.ec.ListenPeerUrls, wcfg.ListenPeerUrls) {
562 t.Errorf("listen-peer-urls = %v, want %v", cfg.ec.ListenPeerUrls, wcfg.ListenPeerUrls)
563 }
564 if !reflect.DeepEqual(cfg.ec.ListenClientUrls, wcfg.ListenClientUrls) {
565 t.Errorf("listen-client-urls = %v, want %v", cfg.ec.ListenClientUrls, wcfg.ListenClientUrls)
566 }
567 if !reflect.DeepEqual(cfg.ec.ListenClientHttpUrls, wcfg.ListenClientHttpUrls) {
568 t.Errorf("listen-client-http-urls = %v, want %v", cfg.ec.ListenClientHttpUrls, wcfg.ListenClientHttpUrls)
569 }
570 }
571
572 func validateClusteringFlags(t *testing.T, cfg *config) {
573 wcfg := newConfig()
574 wcfg.ec.AdvertisePeerUrls = []url.URL{{Scheme: "http", Host: "localhost:8000"}, {Scheme: "https", Host: "localhost:8001"}}
575 wcfg.ec.AdvertiseClientUrls = []url.URL{{Scheme: "http", Host: "localhost:7000"}, {Scheme: "https", Host: "localhost:7001"}}
576 wcfg.ec.ClusterState = embed.ClusterStateFlagExisting
577 wcfg.cf.fallback.Set(fallbackFlagExit)
578 wcfg.ec.InitialCluster = "0=http://localhost:8000"
579 wcfg.ec.InitialClusterToken = "etcdtest"
580
581 if cfg.ec.ClusterState != wcfg.ec.ClusterState {
582 t.Errorf("clusterState = %v, want %v", cfg.ec.ClusterState, wcfg.ec.ClusterState)
583 }
584 if cfg.cf.fallback.String() != wcfg.cf.fallback.String() {
585 t.Errorf("fallback = %v, want %v", cfg.cf.fallback, wcfg.cf.fallback)
586 }
587 if cfg.ec.InitialCluster != wcfg.ec.InitialCluster {
588 t.Errorf("initialCluster = %v, want %v", cfg.ec.InitialCluster, wcfg.ec.InitialCluster)
589 }
590 if cfg.ec.InitialClusterToken != wcfg.ec.InitialClusterToken {
591 t.Errorf("initialClusterToken = %v, want %v", cfg.ec.InitialClusterToken, wcfg.ec.InitialClusterToken)
592 }
593 if !reflect.DeepEqual(cfg.ec.AdvertisePeerUrls, wcfg.ec.AdvertisePeerUrls) {
594 t.Errorf("initial-advertise-peer-urls = %v, want %v", cfg.ec.AdvertisePeerUrls, wcfg.ec.AdvertisePeerUrls)
595 }
596 if !reflect.DeepEqual(cfg.ec.AdvertiseClientUrls, wcfg.ec.AdvertiseClientUrls) {
597 t.Errorf("advertise-client-urls = %v, want %v", cfg.ec.AdvertiseClientUrls, wcfg.ec.AdvertiseClientUrls)
598 }
599 }
600
601 func validateOtherFlags(t *testing.T, cfg *config) {
602 wcfg := newConfig()
603 wcfg.cf.proxy.Set(proxyFlagReadonly)
604 if cfg.cf.proxy.String() != wcfg.cf.proxy.String() {
605 t.Errorf("proxy = %v, want %v", cfg.cf.proxy, wcfg.cf.proxy)
606 }
607 }
608
View as plain text