1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 package embed
16
17 import (
18 "crypto/tls"
19 "fmt"
20 "io/ioutil"
21 "math"
22 "net"
23 "net/http"
24 "net/url"
25 "os"
26 "path/filepath"
27 "strings"
28 "sync"
29 "time"
30
31 "go.etcd.io/etcd/client/pkg/v3/logutil"
32 "go.etcd.io/etcd/client/pkg/v3/srv"
33 "go.etcd.io/etcd/client/pkg/v3/tlsutil"
34 "go.etcd.io/etcd/client/pkg/v3/transport"
35 "go.etcd.io/etcd/client/pkg/v3/types"
36 "go.etcd.io/etcd/pkg/v3/flags"
37 "go.etcd.io/etcd/pkg/v3/netutil"
38 "go.etcd.io/etcd/server/v3/config"
39 "go.etcd.io/etcd/server/v3/etcdserver"
40 "go.etcd.io/etcd/server/v3/etcdserver/api/v3compactor"
41
42 bolt "go.etcd.io/bbolt"
43 "go.uber.org/multierr"
44 "go.uber.org/zap"
45 "golang.org/x/crypto/bcrypt"
46 "google.golang.org/grpc"
47 "sigs.k8s.io/yaml"
48 )
49
50 const (
51 ClusterStateFlagNew = "new"
52 ClusterStateFlagExisting = "existing"
53
54 DefaultName = "default"
55 DefaultMaxSnapshots = 5
56 DefaultMaxWALs = 5
57 DefaultMaxTxnOps = uint(128)
58 DefaultWarningApplyDuration = 100 * time.Millisecond
59 DefaultMaxRequestBytes = 1.5 * 1024 * 1024
60 DefaultMaxConcurrentStreams = math.MaxUint32
61 DefaultGRPCKeepAliveMinTime = 5 * time.Second
62 DefaultGRPCKeepAliveInterval = 2 * time.Hour
63 DefaultGRPCKeepAliveTimeout = 20 * time.Second
64 DefaultDowngradeCheckTime = 5 * time.Second
65
66 DefaultListenPeerURLs = "http://localhost:2380"
67 DefaultListenClientURLs = "http://localhost:2379"
68
69 DefaultLogOutput = "default"
70 JournalLogOutput = "systemd/journal"
71 StdErrLogOutput = "stderr"
72 StdOutLogOutput = "stdout"
73
74
75
76
77
78
79
80
81 DefaultLogRotationConfig = `{"maxsize": 100, "maxage": 0, "maxbackups": 0, "localtime": false, "compress": false}`
82
83
84 ExperimentalDistributedTracingAddress = "localhost:4317"
85
86 ExperimentalDistributedTracingServiceName = "etcd"
87
88
89
90 DefaultStrictReconfigCheck = true
91
92
93 DefaultEnableV2 = false
94
95
96
97 maxElectionMs = 50000
98
99 freelistArrayType = "array"
100 )
101
102 var (
103 ErrConflictBootstrapFlags = fmt.Errorf("multiple discovery or bootstrap flags are set. " +
104 "Choose one of \"initial-cluster\", \"discovery\" or \"discovery-srv\"")
105 ErrUnsetAdvertiseClientURLsFlag = fmt.Errorf("--advertise-client-urls is required when --listen-client-urls is set explicitly")
106 ErrLogRotationInvalidLogOutput = fmt.Errorf("--log-outputs requires a single file path when --log-rotate-config-json is defined")
107
108 DefaultInitialAdvertisePeerURLs = "http://localhost:2380"
109 DefaultAdvertiseClientURLs = "http://localhost:2379"
110
111 defaultHostname string
112 defaultHostStatus error
113
114
115 getCluster = srv.GetCluster
116 )
117
118 var (
119
120
121
122
123
124 CompactorModePeriodic = v3compactor.ModePeriodic
125
126
127
128
129
130
131
132 CompactorModeRevision = v3compactor.ModeRevision
133 )
134
135 func init() {
136 defaultHostname, defaultHostStatus = netutil.GetDefaultHost()
137 }
138
139
140 type Config struct {
141 Name string `json:"name"`
142 Dir string `json:"data-dir"`
143 WalDir string `json:"wal-dir"`
144
145 SnapshotCount uint64 `json:"snapshot-count"`
146
147
148
149
150
151
152
153
154 SnapshotCatchUpEntries uint64
155
156 MaxSnapFiles uint `json:"max-snapshots"`
157 MaxWalFiles uint `json:"max-wals"`
158
159
160
161
162 TickMs uint `json:"heartbeat-interval"`
163 ElectionMs uint `json:"election-timeout"`
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192 InitialElectionTickAdvance bool `json:"initial-election-tick-advance"`
193
194
195 BackendBatchInterval time.Duration `json:"backend-batch-interval"`
196
197 BackendBatchLimit int `json:"backend-batch-limit"`
198
199 BackendFreelistType string `json:"backend-bbolt-freelist-type"`
200 QuotaBackendBytes int64 `json:"quota-backend-bytes"`
201 MaxTxnOps uint `json:"max-txn-ops"`
202 MaxRequestBytes uint `json:"max-request-bytes"`
203
204
205
206 MaxConcurrentStreams uint32 `json:"max-concurrent-streams"`
207
208 ListenPeerUrls, ListenClientUrls, ListenClientHttpUrls []url.URL
209 AdvertisePeerUrls, AdvertiseClientUrls []url.URL
210 ClientTLSInfo transport.TLSInfo
211 ClientAutoTLS bool
212 PeerTLSInfo transport.TLSInfo
213 PeerAutoTLS bool
214
215
216
217 SelfSignedCertValidity uint `json:"self-signed-cert-validity"`
218
219
220
221
222 CipherSuites []string `json:"cipher-suites"`
223
224
225 TlsMinVersion string `json:"tls-min-version"`
226
227 TlsMaxVersion string `json:"tls-max-version"`
228
229 ClusterState string `json:"initial-cluster-state"`
230 DNSCluster string `json:"discovery-srv"`
231 DNSClusterServiceName string `json:"discovery-srv-name"`
232 Dproxy string `json:"discovery-proxy"`
233 Durl string `json:"discovery"`
234 InitialCluster string `json:"initial-cluster"`
235 InitialClusterToken string `json:"initial-cluster-token"`
236 StrictReconfigCheck bool `json:"strict-reconfig-check"`
237
238
239
240
241 EnableV2 bool `json:"enable-v2"`
242
243
244 AutoCompactionMode string `json:"auto-compaction-mode"`
245
246
247
248
249 AutoCompactionRetention string `json:"auto-compaction-retention"`
250
251
252
253
254
255
256
257 GRPCKeepAliveMinTime time.Duration `json:"grpc-keepalive-min-time"`
258
259
260
261 GRPCKeepAliveInterval time.Duration `json:"grpc-keepalive-interval"`
262
263
264 GRPCKeepAliveTimeout time.Duration `json:"grpc-keepalive-timeout"`
265
266
267 SocketOpts transport.SocketOpts `json:"socket-options"`
268
269
270
271
272
273 PreVote bool `json:"pre-vote"`
274
275 CORS map[string]struct{}
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301 HostWhitelist map[string]struct{}
302
303
304
305
306
307 UserHandlers map[string]http.Handler `json:"-"`
308
309
310
311
312
313
314
315 ServiceRegister func(*grpc.Server) `json:"-"`
316
317 AuthToken string `json:"auth-token"`
318 BcryptCost uint `json:"bcrypt-cost"`
319
320
321 AuthTokenTTL uint `json:"auth-token-ttl"`
322
323 ExperimentalInitialCorruptCheck bool `json:"experimental-initial-corrupt-check"`
324 ExperimentalCorruptCheckTime time.Duration `json:"experimental-corrupt-check-time"`
325 ExperimentalCompactHashCheckEnabled bool `json:"experimental-compact-hash-check-enabled"`
326 ExperimentalCompactHashCheckTime time.Duration `json:"experimental-compact-hash-check-time"`
327
328
329
330 ExperimentalEnableV2V3 string `json:"experimental-enable-v2v3"`
331
332 ExperimentalEnableLeaseCheckpoint bool `json:"experimental-enable-lease-checkpoint"`
333
334
335
336
337 ExperimentalEnableLeaseCheckpointPersist bool `json:"experimental-enable-lease-checkpoint-persist"`
338 ExperimentalCompactionBatchLimit int `json:"experimental-compaction-batch-limit"`
339 ExperimentalWatchProgressNotifyInterval time.Duration `json:"experimental-watch-progress-notify-interval"`
340
341
342 ExperimentalWarningApplyDuration time.Duration `json:"experimental-warning-apply-duration"`
343
344
345 ExperimentalBootstrapDefragThresholdMegabytes uint `json:"experimental-bootstrap-defrag-threshold-megabytes"`
346
347
348 ForceNewCluster bool `json:"force-new-cluster"`
349
350 EnablePprof bool `json:"enable-pprof"`
351 Metrics string `json:"metrics"`
352 ListenMetricsUrls []url.URL
353 ListenMetricsUrlsJSON string `json:"listen-metrics-urls"`
354
355
356 ExperimentalEnableDistributedTracing bool `json:"experimental-enable-distributed-tracing"`
357
358
359 ExperimentalDistributedTracingAddress string `json:"experimental-distributed-tracing-address"`
360
361
362 ExperimentalDistributedTracingServiceName string `json:"experimental-distributed-tracing-service-name"`
363
364
365
366
367 ExperimentalDistributedTracingServiceInstanceID string `json:"experimental-distributed-tracing-instance-id"`
368
369
370 ExperimentalDistributedTracingSamplingRatePerMillion int `json:"experimental-distributed-tracing-sampling-rate"`
371
372
373
374 Logger string `json:"logger"`
375
376 LogLevel string `json:"log-level"`
377
378
379
380
381
382
383 LogOutputs []string `json:"log-outputs"`
384
385 EnableLogRotation bool `json:"enable-log-rotation"`
386
387 LogRotationConfigJSON string `json:"log-rotation-config-json"`
388
389 ZapLoggerBuilder func(*Config) error
390
391
392
393
394 loggerMu *sync.RWMutex
395 logger *zap.Logger
396
397
398 EnableGRPCGateway bool `json:"enable-grpc-gateway"`
399
400
401
402 UnsafeNoFsync bool `json:"unsafe-no-fsync"`
403
404 ExperimentalDowngradeCheckTime time.Duration `json:"experimental-downgrade-check-time"`
405
406
407
408
409
410
411
412 ExperimentalMemoryMlock bool `json:"experimental-memory-mlock"`
413
414
415 ExperimentalTxnModeWriteWithSharedBuffer bool `json:"experimental-txn-mode-write-with-shared-buffer"`
416
417
418 V2Deprecation config.V2DeprecationEnum `json:"v2-deprecation"`
419 }
420
421
422 type configYAML struct {
423 Config
424 configJSON
425 }
426
427
428 type configJSON struct {
429 ListenPeerUrls string `json:"listen-peer-urls"`
430 ListenClientUrls string `json:"listen-client-urls"`
431 ListenClientHttpUrls string `json:"listen-client-http-urls"`
432 AdvertisePeerUrls string `json:"initial-advertise-peer-urls"`
433 AdvertiseClientUrls string `json:"advertise-client-urls"`
434
435 CORSJSON string `json:"cors"`
436 HostWhitelistJSON string `json:"host-whitelist"`
437
438 ClientSecurityJSON securityConfig `json:"client-transport-security"`
439 PeerSecurityJSON securityConfig `json:"peer-transport-security"`
440 }
441
442 type securityConfig struct {
443 CertFile string `json:"cert-file"`
444 KeyFile string `json:"key-file"`
445 ClientCertFile string `json:"client-cert-file"`
446 ClientKeyFile string `json:"client-key-file"`
447 CertAuth bool `json:"client-cert-auth"`
448 TrustedCAFile string `json:"trusted-ca-file"`
449 AutoTLS bool `json:"auto-tls"`
450 }
451
452
453 func NewConfig() *Config {
454 lpurl, _ := url.Parse(DefaultListenPeerURLs)
455 apurl, _ := url.Parse(DefaultInitialAdvertisePeerURLs)
456 lcurl, _ := url.Parse(DefaultListenClientURLs)
457 acurl, _ := url.Parse(DefaultAdvertiseClientURLs)
458 cfg := &Config{
459 MaxSnapFiles: DefaultMaxSnapshots,
460 MaxWalFiles: DefaultMaxWALs,
461
462 Name: DefaultName,
463
464 SnapshotCount: etcdserver.DefaultSnapshotCount,
465 SnapshotCatchUpEntries: etcdserver.DefaultSnapshotCatchUpEntries,
466
467 MaxTxnOps: DefaultMaxTxnOps,
468 MaxRequestBytes: DefaultMaxRequestBytes,
469 MaxConcurrentStreams: DefaultMaxConcurrentStreams,
470 ExperimentalWarningApplyDuration: DefaultWarningApplyDuration,
471
472 GRPCKeepAliveMinTime: DefaultGRPCKeepAliveMinTime,
473 GRPCKeepAliveInterval: DefaultGRPCKeepAliveInterval,
474 GRPCKeepAliveTimeout: DefaultGRPCKeepAliveTimeout,
475
476 SocketOpts: transport.SocketOpts{
477 ReusePort: false,
478 ReuseAddress: false,
479 },
480
481 TickMs: 100,
482 ElectionMs: 1000,
483 InitialElectionTickAdvance: true,
484
485 ListenPeerUrls: []url.URL{*lpurl},
486 ListenClientUrls: []url.URL{*lcurl},
487 AdvertisePeerUrls: []url.URL{*apurl},
488 AdvertiseClientUrls: []url.URL{*acurl},
489
490 ClusterState: ClusterStateFlagNew,
491 InitialClusterToken: "etcd-cluster",
492
493 StrictReconfigCheck: DefaultStrictReconfigCheck,
494 Metrics: "basic",
495 EnableV2: DefaultEnableV2,
496
497 CORS: map[string]struct{}{"*": {}},
498 HostWhitelist: map[string]struct{}{"*": {}},
499
500 AuthToken: "simple",
501 BcryptCost: uint(bcrypt.DefaultCost),
502 AuthTokenTTL: 300,
503
504 PreVote: true,
505
506 loggerMu: new(sync.RWMutex),
507 logger: nil,
508 Logger: "zap",
509 LogOutputs: []string{DefaultLogOutput},
510 LogLevel: logutil.DefaultLogLevel,
511 EnableLogRotation: false,
512 LogRotationConfigJSON: DefaultLogRotationConfig,
513 EnableGRPCGateway: true,
514
515 ExperimentalDowngradeCheckTime: DefaultDowngradeCheckTime,
516 ExperimentalMemoryMlock: false,
517 ExperimentalTxnModeWriteWithSharedBuffer: true,
518
519 ExperimentalCompactHashCheckEnabled: false,
520 ExperimentalCompactHashCheckTime: time.Minute,
521
522 V2Deprecation: config.V2_DEPR_DEFAULT,
523 }
524 cfg.InitialCluster = cfg.InitialClusterFromName(cfg.Name)
525 return cfg
526 }
527
528 func ConfigFromFile(path string) (*Config, error) {
529 cfg := &configYAML{Config: *NewConfig()}
530 if err := cfg.configFromFile(path); err != nil {
531 return nil, err
532 }
533 return &cfg.Config, nil
534 }
535
536 func (cfg *configYAML) configFromFile(path string) error {
537 b, err := ioutil.ReadFile(path)
538 if err != nil {
539 return err
540 }
541
542 defaultInitialCluster := cfg.InitialCluster
543
544 err = yaml.Unmarshal(b, cfg)
545 if err != nil {
546 return err
547 }
548
549 if cfg.configJSON.ListenPeerUrls != "" {
550 u, err := types.NewURLs(strings.Split(cfg.configJSON.ListenPeerUrls, ","))
551 if err != nil {
552 fmt.Fprintf(os.Stderr, "unexpected error setting up listen-peer-urls: %v\n", err)
553 os.Exit(1)
554 }
555 cfg.Config.ListenPeerUrls = u
556 }
557
558 if cfg.configJSON.ListenClientUrls != "" {
559 u, err := types.NewURLs(strings.Split(cfg.configJSON.ListenClientUrls, ","))
560 if err != nil {
561 fmt.Fprintf(os.Stderr, "unexpected error setting up listen-client-urls: %v\n", err)
562 os.Exit(1)
563 }
564 cfg.Config.ListenClientUrls = u
565 }
566
567 if cfg.configJSON.ListenClientHttpUrls != "" {
568 u, err := types.NewURLs(strings.Split(cfg.configJSON.ListenClientHttpUrls, ","))
569 if err != nil {
570 fmt.Fprintf(os.Stderr, "unexpected error setting up listen-client-http-urls: %v\n", err)
571 os.Exit(1)
572 }
573 cfg.Config.ListenClientHttpUrls = u
574 }
575
576 if cfg.configJSON.AdvertisePeerUrls != "" {
577 u, err := types.NewURLs(strings.Split(cfg.configJSON.AdvertisePeerUrls, ","))
578 if err != nil {
579 fmt.Fprintf(os.Stderr, "unexpected error setting up initial-advertise-peer-urls: %v\n", err)
580 os.Exit(1)
581 }
582 cfg.Config.AdvertisePeerUrls = u
583 }
584
585 if cfg.configJSON.AdvertiseClientUrls != "" {
586 u, err := types.NewURLs(strings.Split(cfg.configJSON.AdvertiseClientUrls, ","))
587 if err != nil {
588 fmt.Fprintf(os.Stderr, "unexpected error setting up advertise-peer-urls: %v\n", err)
589 os.Exit(1)
590 }
591 cfg.Config.AdvertiseClientUrls = u
592 }
593
594 if cfg.ListenMetricsUrlsJSON != "" {
595 u, err := types.NewURLs(strings.Split(cfg.ListenMetricsUrlsJSON, ","))
596 if err != nil {
597 fmt.Fprintf(os.Stderr, "unexpected error setting up listen-metrics-urls: %v\n", err)
598 os.Exit(1)
599 }
600 cfg.ListenMetricsUrls = []url.URL(u)
601 }
602
603 if cfg.CORSJSON != "" {
604 uv := flags.NewUniqueURLsWithExceptions(cfg.CORSJSON, "*")
605 cfg.CORS = uv.Values
606 }
607
608 if cfg.HostWhitelistJSON != "" {
609 uv := flags.NewUniqueStringsValue(cfg.HostWhitelistJSON)
610 cfg.HostWhitelist = uv.Values
611 }
612
613
614 if (cfg.Durl != "" || cfg.DNSCluster != "") && cfg.InitialCluster == defaultInitialCluster {
615 cfg.InitialCluster = ""
616 }
617 if cfg.ClusterState == "" {
618 cfg.ClusterState = ClusterStateFlagNew
619 }
620
621 copySecurityDetails := func(tls *transport.TLSInfo, ysc *securityConfig) {
622 tls.CertFile = ysc.CertFile
623 tls.KeyFile = ysc.KeyFile
624 tls.ClientCertFile = ysc.ClientCertFile
625 tls.ClientKeyFile = ysc.ClientKeyFile
626 tls.ClientCertAuth = ysc.CertAuth
627 tls.TrustedCAFile = ysc.TrustedCAFile
628 }
629 copySecurityDetails(&cfg.ClientTLSInfo, &cfg.ClientSecurityJSON)
630 copySecurityDetails(&cfg.PeerTLSInfo, &cfg.PeerSecurityJSON)
631 cfg.ClientAutoTLS = cfg.ClientSecurityJSON.AutoTLS
632 cfg.PeerAutoTLS = cfg.PeerSecurityJSON.AutoTLS
633 if cfg.SelfSignedCertValidity == 0 {
634 cfg.SelfSignedCertValidity = 1
635 }
636 return cfg.Validate()
637 }
638
639 func updateCipherSuites(tls *transport.TLSInfo, ss []string) error {
640 if len(tls.CipherSuites) > 0 && len(ss) > 0 {
641 return fmt.Errorf("TLSInfo.CipherSuites is already specified (given %v)", ss)
642 }
643 if len(ss) > 0 {
644 cs, err := tlsutil.GetCipherSuites(ss)
645 if err != nil {
646 return err
647 }
648 tls.CipherSuites = cs
649 }
650 return nil
651 }
652
653 func updateMinMaxVersions(info *transport.TLSInfo, min, max string) {
654
655 var err error
656 if info.MinVersion, err = tlsutil.GetTLSVersion(min); err != nil {
657 panic(err)
658 }
659 if info.MaxVersion, err = tlsutil.GetTLSVersion(max); err != nil {
660 panic(err)
661 }
662 }
663
664
665 func (cfg *Config) Validate() error {
666 if err := cfg.setupLogging(); err != nil {
667 return err
668 }
669 if err := checkBindURLs(cfg.ListenPeerUrls); err != nil {
670 return err
671 }
672 if err := checkBindURLs(cfg.ListenClientUrls); err != nil {
673 return err
674 }
675 if err := checkBindURLs(cfg.ListenClientHttpUrls); err != nil {
676 return err
677 }
678 if len(cfg.ListenClientHttpUrls) == 0 {
679 cfg.logger.Warn("Running http and grpc server on single port. This is not recommended for production.")
680 }
681 if err := checkBindURLs(cfg.ListenMetricsUrls); err != nil {
682 return err
683 }
684 if err := checkHostURLs(cfg.AdvertisePeerUrls); err != nil {
685 addrs := cfg.getAdvertisePeerUrls()
686 return fmt.Errorf(`--initial-advertise-peer-urls %q must be "host:port" (%v)`, strings.Join(addrs, ","), err)
687 }
688 if err := checkHostURLs(cfg.AdvertiseClientUrls); err != nil {
689 addrs := cfg.getAdvertiseClientUrls()
690 return fmt.Errorf(`--advertise-client-urls %q must be "host:port" (%v)`, strings.Join(addrs, ","), err)
691 }
692
693 nSet := 0
694 for _, v := range []bool{cfg.Durl != "", cfg.InitialCluster != "", cfg.DNSCluster != ""} {
695 if v {
696 nSet++
697 }
698 }
699
700 if cfg.ClusterState != ClusterStateFlagNew && cfg.ClusterState != ClusterStateFlagExisting {
701 return fmt.Errorf("unexpected clusterState %q", cfg.ClusterState)
702 }
703
704 if nSet > 1 {
705 return ErrConflictBootstrapFlags
706 }
707
708 if cfg.TickMs == 0 {
709 return fmt.Errorf("--heartbeat-interval must be >0 (set to %dms)", cfg.TickMs)
710 }
711 if cfg.ElectionMs == 0 {
712 return fmt.Errorf("--election-timeout must be >0 (set to %dms)", cfg.ElectionMs)
713 }
714 if 5*cfg.TickMs > cfg.ElectionMs {
715 return fmt.Errorf("--election-timeout[%vms] should be at least as 5 times as --heartbeat-interval[%vms]", cfg.ElectionMs, cfg.TickMs)
716 }
717 if cfg.ElectionMs > maxElectionMs {
718 return fmt.Errorf("--election-timeout[%vms] is too long, and should be set less than %vms", cfg.ElectionMs, maxElectionMs)
719 }
720
721
722 if cfg.ListenClientUrls != nil && cfg.AdvertiseClientUrls == nil {
723 return ErrUnsetAdvertiseClientURLsFlag
724 }
725
726 switch cfg.AutoCompactionMode {
727 case "":
728 case CompactorModeRevision, CompactorModePeriodic:
729 default:
730 return fmt.Errorf("unknown auto-compaction-mode %q", cfg.AutoCompactionMode)
731 }
732
733 if !cfg.ExperimentalEnableLeaseCheckpointPersist && cfg.ExperimentalEnableLeaseCheckpoint {
734 cfg.logger.Warn("Detected that checkpointing is enabled without persistence. Consider enabling experimental-enable-lease-checkpoint-persist")
735 }
736
737 if cfg.ExperimentalEnableLeaseCheckpointPersist && !cfg.ExperimentalEnableLeaseCheckpoint {
738 return fmt.Errorf("setting experimental-enable-lease-checkpoint-persist requires experimental-enable-lease-checkpoint")
739 }
740
741 if cfg.ExperimentalCompactHashCheckTime <= 0 {
742 return fmt.Errorf("--experimental-compact-hash-check-time must be >0 (set to %v)", cfg.ExperimentalCompactHashCheckTime)
743 }
744
745 minVersion, err := tlsutil.GetTLSVersion(cfg.TlsMinVersion)
746 if err != nil {
747 return err
748 }
749 maxVersion, err := tlsutil.GetTLSVersion(cfg.TlsMaxVersion)
750 if err != nil {
751 return err
752 }
753
754
755 if maxVersion != 0 && minVersion > maxVersion {
756 return fmt.Errorf("min version (%s) is greater than max version (%s)", cfg.TlsMinVersion, cfg.TlsMaxVersion)
757 }
758
759
760 if minVersion == tls.VersionTLS13 && len(cfg.CipherSuites) > 0 {
761 return fmt.Errorf("cipher suites cannot be configured when only TLS1.3 is enabled")
762 }
763
764
765 if cfg.ExperimentalEnableDistributedTracing {
766 if err := validateTracingConfig(cfg.ExperimentalDistributedTracingSamplingRatePerMillion); err != nil {
767 return fmt.Errorf("distributed tracing configurition is not valid: (%v)", err)
768 }
769 }
770
771 return nil
772 }
773
774
775 func (cfg *Config) PeerURLsMapAndToken(which string) (urlsmap types.URLsMap, token string, err error) {
776 token = cfg.InitialClusterToken
777 switch {
778 case cfg.Durl != "":
779 urlsmap = types.URLsMap{}
780
781
782 urlsmap[cfg.Name] = cfg.AdvertisePeerUrls
783 token = cfg.Durl
784
785 case cfg.DNSCluster != "":
786 clusterStrs, cerr := cfg.GetDNSClusterNames()
787 lg := cfg.logger
788 if cerr != nil {
789 lg.Warn("failed to resolve during SRV discovery", zap.Error(cerr))
790 }
791 if len(clusterStrs) == 0 {
792 return nil, "", cerr
793 }
794 for _, s := range clusterStrs {
795 lg.Info("got bootstrap from DNS for etcd-server", zap.String("node", s))
796 }
797 clusterStr := strings.Join(clusterStrs, ",")
798 if strings.Contains(clusterStr, "https://") && cfg.PeerTLSInfo.TrustedCAFile == "" {
799 cfg.PeerTLSInfo.ServerName = cfg.DNSCluster
800 }
801 urlsmap, err = types.NewURLsMap(clusterStr)
802
803
804 if which == "etcd" {
805 if _, ok := urlsmap[cfg.Name]; !ok {
806 return nil, "", fmt.Errorf("cannot find local etcd member %q in SRV records", cfg.Name)
807 }
808 }
809
810 default:
811
812 urlsmap, err = types.NewURLsMap(cfg.InitialCluster)
813 }
814 return urlsmap, token, err
815 }
816
817
818
819
820
821
822 func (cfg *Config) GetDNSClusterNames() ([]string, error) {
823 var (
824 clusterStrs []string
825 cerr error
826 serviceNameSuffix string
827 )
828 if cfg.DNSClusterServiceName != "" {
829 serviceNameSuffix = "-" + cfg.DNSClusterServiceName
830 }
831
832 lg := cfg.GetLogger()
833
834
835
836 clusterStrs, cerr = getCluster("https", "etcd-server-ssl"+serviceNameSuffix, cfg.Name, cfg.DNSCluster, cfg.AdvertisePeerUrls)
837 if cerr != nil {
838 clusterStrs = make([]string, 0)
839 }
840 lg.Info(
841 "get cluster for etcd-server-ssl SRV",
842 zap.String("service-scheme", "https"),
843 zap.String("service-name", "etcd-server-ssl"+serviceNameSuffix),
844 zap.String("server-name", cfg.Name),
845 zap.String("discovery-srv", cfg.DNSCluster),
846 zap.Strings("advertise-peer-urls", cfg.getAdvertisePeerUrls()),
847 zap.Strings("found-cluster", clusterStrs),
848 zap.Error(cerr),
849 )
850
851 defaultHTTPClusterStrs, httpCerr := getCluster("http", "etcd-server"+serviceNameSuffix, cfg.Name, cfg.DNSCluster, cfg.AdvertisePeerUrls)
852 if httpCerr == nil {
853 clusterStrs = append(clusterStrs, defaultHTTPClusterStrs...)
854 }
855 lg.Info(
856 "get cluster for etcd-server SRV",
857 zap.String("service-scheme", "http"),
858 zap.String("service-name", "etcd-server"+serviceNameSuffix),
859 zap.String("server-name", cfg.Name),
860 zap.String("discovery-srv", cfg.DNSCluster),
861 zap.Strings("advertise-peer-urls", cfg.getAdvertisePeerUrls()),
862 zap.Strings("found-cluster", clusterStrs),
863 zap.Error(httpCerr),
864 )
865
866 return clusterStrs, multierr.Combine(cerr, httpCerr)
867 }
868
869 func (cfg Config) InitialClusterFromName(name string) (ret string) {
870 if len(cfg.AdvertisePeerUrls) == 0 {
871 return ""
872 }
873 n := name
874 if name == "" {
875 n = DefaultName
876 }
877 for i := range cfg.AdvertisePeerUrls {
878 ret = ret + "," + n + "=" + cfg.AdvertisePeerUrls[i].String()
879 }
880 return ret[1:]
881 }
882
883 func (cfg Config) IsNewCluster() bool { return cfg.ClusterState == ClusterStateFlagNew }
884 func (cfg Config) ElectionTicks() int { return int(cfg.ElectionMs / cfg.TickMs) }
885
886 func (cfg Config) V2DeprecationEffective() config.V2DeprecationEnum {
887 if cfg.V2Deprecation == "" {
888 return config.V2_DEPR_DEFAULT
889 }
890 return cfg.V2Deprecation
891 }
892
893 func (cfg Config) defaultPeerHost() bool {
894 return len(cfg.AdvertisePeerUrls) == 1 && cfg.AdvertisePeerUrls[0].String() == DefaultInitialAdvertisePeerURLs
895 }
896
897 func (cfg Config) defaultClientHost() bool {
898 return len(cfg.AdvertiseClientUrls) == 1 && cfg.AdvertiseClientUrls[0].String() == DefaultAdvertiseClientURLs
899 }
900
901 func (cfg *Config) ClientSelfCert() (err error) {
902 if !cfg.ClientAutoTLS {
903 return nil
904 }
905 if !cfg.ClientTLSInfo.Empty() {
906 cfg.logger.Warn("ignoring client auto TLS since certs given")
907 return nil
908 }
909 chosts := make([]string, 0, len(cfg.ListenClientUrls)+len(cfg.ListenClientHttpUrls))
910 for _, u := range cfg.ListenClientUrls {
911 chosts = append(chosts, u.Host)
912 }
913 for _, u := range cfg.ListenClientHttpUrls {
914 chosts = append(chosts, u.Host)
915 }
916 cfg.ClientTLSInfo, err = transport.SelfCert(cfg.logger, filepath.Join(cfg.Dir, "fixtures", "client"), chosts, cfg.SelfSignedCertValidity)
917 if err != nil {
918 return err
919 }
920 return updateCipherSuites(&cfg.ClientTLSInfo, cfg.CipherSuites)
921 }
922
923 func (cfg *Config) PeerSelfCert() (err error) {
924 if !cfg.PeerAutoTLS {
925 return nil
926 }
927 if !cfg.PeerTLSInfo.Empty() {
928 cfg.logger.Warn("ignoring peer auto TLS since certs given")
929 return nil
930 }
931 phosts := make([]string, len(cfg.ListenPeerUrls))
932 for i, u := range cfg.ListenPeerUrls {
933 phosts[i] = u.Host
934 }
935 cfg.PeerTLSInfo, err = transport.SelfCert(cfg.logger, filepath.Join(cfg.Dir, "fixtures", "peer"), phosts, cfg.SelfSignedCertValidity)
936 if err != nil {
937 return err
938 }
939 return updateCipherSuites(&cfg.PeerTLSInfo, cfg.CipherSuites)
940 }
941
942
943
944
945
946
947
948
949
950 func (cfg *Config) UpdateDefaultClusterFromName(defaultInitialCluster string) (string, error) {
951 if defaultHostname == "" || defaultHostStatus != nil {
952
953 if cfg.Name != DefaultName && cfg.InitialCluster == defaultInitialCluster {
954 cfg.InitialCluster = cfg.InitialClusterFromName(cfg.Name)
955 }
956 return "", defaultHostStatus
957 }
958
959 used := false
960 pip, pport := cfg.ListenPeerUrls[0].Hostname(), cfg.ListenPeerUrls[0].Port()
961 if cfg.defaultPeerHost() && pip == "0.0.0.0" {
962 cfg.AdvertisePeerUrls[0] = url.URL{Scheme: cfg.AdvertisePeerUrls[0].Scheme, Host: fmt.Sprintf("%s:%s", defaultHostname, pport)}
963 used = true
964 }
965
966 if cfg.Name != DefaultName && cfg.InitialCluster == defaultInitialCluster {
967 cfg.InitialCluster = cfg.InitialClusterFromName(cfg.Name)
968 }
969
970 cip, cport := cfg.ListenClientUrls[0].Hostname(), cfg.ListenClientUrls[0].Port()
971 if cfg.defaultClientHost() && cip == "0.0.0.0" {
972 cfg.AdvertiseClientUrls[0] = url.URL{Scheme: cfg.AdvertiseClientUrls[0].Scheme, Host: fmt.Sprintf("%s:%s", defaultHostname, cport)}
973 used = true
974 }
975 dhost := defaultHostname
976 if !used {
977 dhost = ""
978 }
979 return dhost, defaultHostStatus
980 }
981
982
983 func checkBindURLs(urls []url.URL) error {
984 for _, url := range urls {
985 if url.Scheme == "unix" || url.Scheme == "unixs" {
986 continue
987 }
988 host, _, err := net.SplitHostPort(url.Host)
989 if err != nil {
990 return err
991 }
992 if host == "localhost" {
993
994
995 continue
996 }
997 if net.ParseIP(host) == nil {
998 return fmt.Errorf("expected IP in URL for binding (%s)", url.String())
999 }
1000 }
1001 return nil
1002 }
1003
1004 func checkHostURLs(urls []url.URL) error {
1005 for _, url := range urls {
1006 host, _, err := net.SplitHostPort(url.Host)
1007 if err != nil {
1008 return err
1009 }
1010 if host == "" {
1011 return fmt.Errorf("unexpected empty host (%s)", url.String())
1012 }
1013 }
1014 return nil
1015 }
1016
1017 func (cfg *Config) getAdvertisePeerUrls() (ss []string) {
1018 ss = make([]string, len(cfg.AdvertisePeerUrls))
1019 for i := range cfg.AdvertisePeerUrls {
1020 ss[i] = cfg.AdvertisePeerUrls[i].String()
1021 }
1022 return ss
1023 }
1024
1025 func (cfg *Config) getListenPeerUrls() (ss []string) {
1026 ss = make([]string, len(cfg.ListenPeerUrls))
1027 for i := range cfg.ListenPeerUrls {
1028 ss[i] = cfg.ListenPeerUrls[i].String()
1029 }
1030 return ss
1031 }
1032
1033 func (cfg *Config) getAdvertiseClientUrls() (ss []string) {
1034 ss = make([]string, len(cfg.AdvertiseClientUrls))
1035 for i := range cfg.AdvertiseClientUrls {
1036 ss[i] = cfg.AdvertiseClientUrls[i].String()
1037 }
1038 return ss
1039 }
1040
1041 func (cfg *Config) getListenClientUrls() (ss []string) {
1042 ss = make([]string, len(cfg.ListenClientUrls))
1043 for i := range cfg.ListenClientUrls {
1044 ss[i] = cfg.ListenClientUrls[i].String()
1045 }
1046 return ss
1047 }
1048
1049 func (cfg *Config) getListenClientHttpUrls() (ss []string) {
1050 ss = make([]string, len(cfg.ListenClientHttpUrls))
1051 for i := range cfg.ListenClientHttpUrls {
1052 ss[i] = cfg.ListenClientHttpUrls[i].String()
1053 }
1054 return ss
1055 }
1056
1057 func (cfg *Config) getMetricsURLs() (ss []string) {
1058 ss = make([]string, len(cfg.ListenMetricsUrls))
1059 for i := range cfg.ListenMetricsUrls {
1060 ss[i] = cfg.ListenMetricsUrls[i].String()
1061 }
1062 return ss
1063 }
1064
1065 func parseBackendFreelistType(freelistType string) bolt.FreelistType {
1066 if freelistType == freelistArrayType {
1067 return bolt.FreelistArrayType
1068 }
1069
1070 return bolt.FreelistMapType
1071 }
1072
View as plain text