blob: 2f64d927f2afcaf1cafb246989d3e66dcb728878 [file] [log] [blame]
khenaidoo59ce9dd2019-11-11 13:05:32 -05001// Copyright 2016 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
15package embed
16
17import (
18 "crypto/tls"
19 "fmt"
20 "io/ioutil"
21 "net"
22 "net/http"
23 "net/url"
24 "os"
25 "path/filepath"
26 "strings"
27 "sync"
28 "time"
29
30 "go.etcd.io/etcd/etcdserver"
31 "go.etcd.io/etcd/etcdserver/api/v3compactor"
32 "go.etcd.io/etcd/pkg/flags"
33 "go.etcd.io/etcd/pkg/logutil"
34 "go.etcd.io/etcd/pkg/netutil"
35 "go.etcd.io/etcd/pkg/srv"
36 "go.etcd.io/etcd/pkg/tlsutil"
37 "go.etcd.io/etcd/pkg/transport"
38 "go.etcd.io/etcd/pkg/types"
39
40 bolt "go.etcd.io/bbolt"
41 "go.uber.org/zap"
42 "go.uber.org/zap/zapcore"
43 "golang.org/x/crypto/bcrypt"
44 "google.golang.org/grpc"
45 "sigs.k8s.io/yaml"
46)
47
48const (
49 ClusterStateFlagNew = "new"
50 ClusterStateFlagExisting = "existing"
51
52 DefaultName = "default"
53 DefaultMaxSnapshots = 5
54 DefaultMaxWALs = 5
55 DefaultMaxTxnOps = uint(128)
56 DefaultMaxRequestBytes = 1.5 * 1024 * 1024
57 DefaultGRPCKeepAliveMinTime = 5 * time.Second
58 DefaultGRPCKeepAliveInterval = 2 * time.Hour
59 DefaultGRPCKeepAliveTimeout = 20 * time.Second
60
61 DefaultListenPeerURLs = "http://localhost:2380"
62 DefaultListenClientURLs = "http://localhost:2379"
63
64 DefaultLogOutput = "default"
65 JournalLogOutput = "systemd/journal"
66 StdErrLogOutput = "stderr"
67 StdOutLogOutput = "stdout"
68
69 // DefaultStrictReconfigCheck is the default value for "--strict-reconfig-check" flag.
70 // It's enabled by default.
71 DefaultStrictReconfigCheck = true
72 // DefaultEnableV2 is the default value for "--enable-v2" flag.
73 // v2 API is disabled by default.
74 DefaultEnableV2 = false
75
76 // maxElectionMs specifies the maximum value of election timeout.
77 // More details are listed in ../Documentation/tuning.md#time-parameters.
78 maxElectionMs = 50000
79 // backend freelist map type
80 freelistMapType = "map"
81)
82
83var (
84 ErrConflictBootstrapFlags = fmt.Errorf("multiple discovery or bootstrap flags are set. " +
85 "Choose one of \"initial-cluster\", \"discovery\" or \"discovery-srv\"")
86 ErrUnsetAdvertiseClientURLsFlag = fmt.Errorf("--advertise-client-urls is required when --listen-client-urls is set explicitly")
87
88 DefaultInitialAdvertisePeerURLs = "http://localhost:2380"
89 DefaultAdvertiseClientURLs = "http://localhost:2379"
90
91 defaultHostname string
92 defaultHostStatus error
93)
94
95var (
96 // CompactorModePeriodic is periodic compaction mode
97 // for "Config.AutoCompactionMode" field.
98 // If "AutoCompactionMode" is CompactorModePeriodic and
99 // "AutoCompactionRetention" is "1h", it automatically compacts
100 // compacts storage every hour.
101 CompactorModePeriodic = v3compactor.ModePeriodic
102
103 // CompactorModeRevision is revision-based compaction mode
104 // for "Config.AutoCompactionMode" field.
105 // If "AutoCompactionMode" is CompactorModeRevision and
106 // "AutoCompactionRetention" is "1000", it compacts log on
107 // revision 5000 when the current revision is 6000.
108 // This runs every 5-minute if enough of logs have proceeded.
109 CompactorModeRevision = v3compactor.ModeRevision
110)
111
112func init() {
113 defaultHostname, defaultHostStatus = netutil.GetDefaultHost()
114}
115
116// Config holds the arguments for configuring an etcd server.
117type Config struct {
118 Name string `json:"name"`
119 Dir string `json:"data-dir"`
120 WalDir string `json:"wal-dir"`
121
122 SnapshotCount uint64 `json:"snapshot-count"`
123
124 // SnapshotCatchUpEntries is the number of entries for a slow follower
125 // to catch-up after compacting the raft storage entries.
126 // We expect the follower has a millisecond level latency with the leader.
127 // The max throughput is around 10K. Keep a 5K entries is enough for helping
128 // follower to catch up.
129 // WARNING: only change this for tests.
130 // Always use "DefaultSnapshotCatchUpEntries"
131 SnapshotCatchUpEntries uint64
132
133 MaxSnapFiles uint `json:"max-snapshots"`
134 MaxWalFiles uint `json:"max-wals"`
135
136 // TickMs is the number of milliseconds between heartbeat ticks.
137 // TODO: decouple tickMs and heartbeat tick (current heartbeat tick = 1).
138 // make ticks a cluster wide configuration.
139 TickMs uint `json:"heartbeat-interval"`
140 ElectionMs uint `json:"election-timeout"`
141
142 // InitialElectionTickAdvance is true, then local member fast-forwards
143 // election ticks to speed up "initial" leader election trigger. This
144 // benefits the case of larger election ticks. For instance, cross
145 // datacenter deployment may require longer election timeout of 10-second.
146 // If true, local node does not need wait up to 10-second. Instead,
147 // forwards its election ticks to 8-second, and have only 2-second left
148 // before leader election.
149 //
150 // Major assumptions are that:
151 // - cluster has no active leader thus advancing ticks enables faster
152 // leader election, or
153 // - cluster already has an established leader, and rejoining follower
154 // is likely to receive heartbeats from the leader after tick advance
155 // and before election timeout.
156 //
157 // However, when network from leader to rejoining follower is congested,
158 // and the follower does not receive leader heartbeat within left election
159 // ticks, disruptive election has to happen thus affecting cluster
160 // availabilities.
161 //
162 // Disabling this would slow down initial bootstrap process for cross
163 // datacenter deployments. Make your own tradeoffs by configuring
164 // --initial-election-tick-advance at the cost of slow initial bootstrap.
165 //
166 // If single-node, it advances ticks regardless.
167 //
168 // See https://github.com/etcd-io/etcd/issues/9333 for more detail.
169 InitialElectionTickAdvance bool `json:"initial-election-tick-advance"`
170
171 // BackendBatchInterval is the maximum time before commit the backend transaction.
172 BackendBatchInterval time.Duration `json:"backend-batch-interval"`
173 // BackendBatchLimit is the maximum operations before commit the backend transaction.
174 BackendBatchLimit int `json:"backend-batch-limit"`
175 QuotaBackendBytes int64 `json:"quota-backend-bytes"`
176 MaxTxnOps uint `json:"max-txn-ops"`
177 MaxRequestBytes uint `json:"max-request-bytes"`
178
179 LPUrls, LCUrls []url.URL
180 APUrls, ACUrls []url.URL
181 ClientTLSInfo transport.TLSInfo
182 ClientAutoTLS bool
183 PeerTLSInfo transport.TLSInfo
184 PeerAutoTLS bool
185
186 // CipherSuites is a list of supported TLS cipher suites between
187 // client/server and peers. If empty, Go auto-populates the list.
188 // Note that cipher suites are prioritized in the given order.
189 CipherSuites []string `json:"cipher-suites"`
190
191 ClusterState string `json:"initial-cluster-state"`
192 DNSCluster string `json:"discovery-srv"`
193 DNSClusterServiceName string `json:"discovery-srv-name"`
194 Dproxy string `json:"discovery-proxy"`
195 Durl string `json:"discovery"`
196 InitialCluster string `json:"initial-cluster"`
197 InitialClusterToken string `json:"initial-cluster-token"`
198 StrictReconfigCheck bool `json:"strict-reconfig-check"`
199 EnableV2 bool `json:"enable-v2"`
200
201 // AutoCompactionMode is either 'periodic' or 'revision'.
202 AutoCompactionMode string `json:"auto-compaction-mode"`
203 // AutoCompactionRetention is either duration string with time unit
204 // (e.g. '5m' for 5-minute), or revision unit (e.g. '5000').
205 // If no time unit is provided and compaction mode is 'periodic',
206 // the unit defaults to hour. For example, '5' translates into 5-hour.
207 AutoCompactionRetention string `json:"auto-compaction-retention"`
208
209 // GRPCKeepAliveMinTime is the minimum interval that a client should
210 // wait before pinging server. When client pings "too fast", server
211 // sends goaway and closes the connection (errors: too_many_pings,
212 // http2.ErrCodeEnhanceYourCalm). When too slow, nothing happens.
213 // Server expects client pings only when there is any active streams
214 // (PermitWithoutStream is set false).
215 GRPCKeepAliveMinTime time.Duration `json:"grpc-keepalive-min-time"`
216 // GRPCKeepAliveInterval is the frequency of server-to-client ping
217 // to check if a connection is alive. Close a non-responsive connection
218 // after an additional duration of Timeout. 0 to disable.
219 GRPCKeepAliveInterval time.Duration `json:"grpc-keepalive-interval"`
220 // GRPCKeepAliveTimeout is the additional duration of wait
221 // before closing a non-responsive connection. 0 to disable.
222 GRPCKeepAliveTimeout time.Duration `json:"grpc-keepalive-timeout"`
223
224 // PreVote is true to enable Raft Pre-Vote.
225 // If enabled, Raft runs an additional election phase
226 // to check whether it would get enough votes to win
227 // an election, thus minimizing disruptions.
228 // TODO: enable by default in 3.5.
229 PreVote bool `json:"pre-vote"`
230
231 CORS map[string]struct{}
232
233 // HostWhitelist lists acceptable hostnames from HTTP client requests.
234 // Client origin policy protects against "DNS Rebinding" attacks
235 // to insecure etcd servers. That is, any website can simply create
236 // an authorized DNS name, and direct DNS to "localhost" (or any
237 // other address). Then, all HTTP endpoints of etcd server listening
238 // on "localhost" becomes accessible, thus vulnerable to DNS rebinding
239 // attacks. See "CVE-2018-5702" for more detail.
240 //
241 // 1. If client connection is secure via HTTPS, allow any hostnames.
242 // 2. If client connection is not secure and "HostWhitelist" is not empty,
243 // only allow HTTP requests whose Host field is listed in whitelist.
244 //
245 // Note that the client origin policy is enforced whether authentication
246 // is enabled or not, for tighter controls.
247 //
248 // By default, "HostWhitelist" is "*", which allows any hostnames.
249 // Note that when specifying hostnames, loopback addresses are not added
250 // automatically. To allow loopback interfaces, leave it empty or set it "*",
251 // or add them to whitelist manually (e.g. "localhost", "127.0.0.1", etc.).
252 //
253 // CVE-2018-5702 reference:
254 // - https://bugs.chromium.org/p/project-zero/issues/detail?id=1447#c2
255 // - https://github.com/transmission/transmission/pull/468
256 // - https://github.com/etcd-io/etcd/issues/9353
257 HostWhitelist map[string]struct{}
258
259 // UserHandlers is for registering users handlers and only used for
260 // embedding etcd into other applications.
261 // The map key is the route path for the handler, and
262 // you must ensure it can't be conflicted with etcd's.
263 UserHandlers map[string]http.Handler `json:"-"`
264 // ServiceRegister is for registering users' gRPC services. A simple usage example:
265 // cfg := embed.NewConfig()
266 // cfg.ServerRegister = func(s *grpc.Server) {
267 // pb.RegisterFooServer(s, &fooServer{})
268 // pb.RegisterBarServer(s, &barServer{})
269 // }
270 // embed.StartEtcd(cfg)
271 ServiceRegister func(*grpc.Server) `json:"-"`
272
273 AuthToken string `json:"auth-token"`
274 BcryptCost uint `json:"bcrypt-cost"`
275
276 ExperimentalInitialCorruptCheck bool `json:"experimental-initial-corrupt-check"`
277 ExperimentalCorruptCheckTime time.Duration `json:"experimental-corrupt-check-time"`
278 ExperimentalEnableV2V3 string `json:"experimental-enable-v2v3"`
279 // ExperimentalBackendFreelistType specifies the type of freelist that boltdb backend uses (array and map are supported types).
280 ExperimentalBackendFreelistType string `json:"experimental-backend-bbolt-freelist-type"`
281 // ExperimentalEnableLeaseCheckpoint enables primary lessor to persist lease remainingTTL to prevent indefinite auto-renewal of long lived leases.
282 ExperimentalEnableLeaseCheckpoint bool `json:"experimental-enable-lease-checkpoint"`
283 ExperimentalCompactionBatchLimit int `json:"experimental-compaction-batch-limit"`
284
285 // ForceNewCluster starts a new cluster even if previously started; unsafe.
286 ForceNewCluster bool `json:"force-new-cluster"`
287
288 EnablePprof bool `json:"enable-pprof"`
289 Metrics string `json:"metrics"`
290 ListenMetricsUrls []url.URL
291 ListenMetricsUrlsJSON string `json:"listen-metrics-urls"`
292
293 // Logger is logger options: "zap", "capnslog".
294 // WARN: "capnslog" is being deprecated in v3.5.
295 Logger string `json:"logger"`
296 // LogLevel configures log level. Only supports debug, info, warn, error, panic, or fatal. Default 'info'.
297 LogLevel string `json:"log-level"`
298 // LogOutputs is either:
299 // - "default" as os.Stderr,
300 // - "stderr" as os.Stderr,
301 // - "stdout" as os.Stdout,
302 // - file path to append server logs to.
303 // It can be multiple when "Logger" is zap.
304 LogOutputs []string `json:"log-outputs"`
305
306 // ZapLoggerBuilder is used to build the zap logger.
307 ZapLoggerBuilder func(*Config) error
308
309 // logger logs server-side operations. The default is nil,
310 // and "setupLogging" must be called before starting server.
311 // Do not set logger directly.
312 loggerMu *sync.RWMutex
313 logger *zap.Logger
314
315 // loggerConfig is server logger configuration for Raft logger.
316 // Must be either: "loggerConfig != nil" or "loggerCore != nil && loggerWriteSyncer != nil".
317 loggerConfig *zap.Config
318 // loggerCore is "zapcore.Core" for raft logger.
319 // Must be either: "loggerConfig != nil" or "loggerCore != nil && loggerWriteSyncer != nil".
320 loggerCore zapcore.Core
321 loggerWriteSyncer zapcore.WriteSyncer
322
323 // EnableGRPCGateway is false to disable grpc gateway.
324 EnableGRPCGateway bool `json:"enable-grpc-gateway"`
325
326 // TO BE DEPRECATED
327
328 // DeprecatedLogOutput is to be deprecated in v3.5.
329 // Just here for safe migration in v3.4.
330 DeprecatedLogOutput []string `json:"log-output"`
331 // Debug is true, to enable debug level logging.
332 // WARNING: to be deprecated in 3.5. Use "--log-level=debug" instead.
333 Debug bool `json:"debug"`
334 // LogPkgLevels is being deprecated in v3.5.
335 // Only valid if "logger" option is "capnslog".
336 // WARN: DO NOT USE THIS!
337 LogPkgLevels string `json:"log-package-levels"`
338}
339
340// configYAML holds the config suitable for yaml parsing
341type configYAML struct {
342 Config
343 configJSON
344}
345
346// configJSON has file options that are translated into Config options
347type configJSON struct {
348 LPUrlsJSON string `json:"listen-peer-urls"`
349 LCUrlsJSON string `json:"listen-client-urls"`
350 APUrlsJSON string `json:"initial-advertise-peer-urls"`
351 ACUrlsJSON string `json:"advertise-client-urls"`
352
353 CORSJSON string `json:"cors"`
354 HostWhitelistJSON string `json:"host-whitelist"`
355
356 ClientSecurityJSON securityConfig `json:"client-transport-security"`
357 PeerSecurityJSON securityConfig `json:"peer-transport-security"`
358}
359
360type securityConfig struct {
361 CertFile string `json:"cert-file"`
362 KeyFile string `json:"key-file"`
363 CertAuth bool `json:"client-cert-auth"`
364 TrustedCAFile string `json:"trusted-ca-file"`
365 AutoTLS bool `json:"auto-tls"`
366}
367
368// NewConfig creates a new Config populated with default values.
369func NewConfig() *Config {
370 lpurl, _ := url.Parse(DefaultListenPeerURLs)
371 apurl, _ := url.Parse(DefaultInitialAdvertisePeerURLs)
372 lcurl, _ := url.Parse(DefaultListenClientURLs)
373 acurl, _ := url.Parse(DefaultAdvertiseClientURLs)
374 cfg := &Config{
375 MaxSnapFiles: DefaultMaxSnapshots,
376 MaxWalFiles: DefaultMaxWALs,
377
378 Name: DefaultName,
379
380 SnapshotCount: etcdserver.DefaultSnapshotCount,
381 SnapshotCatchUpEntries: etcdserver.DefaultSnapshotCatchUpEntries,
382
383 MaxTxnOps: DefaultMaxTxnOps,
384 MaxRequestBytes: DefaultMaxRequestBytes,
385
386 GRPCKeepAliveMinTime: DefaultGRPCKeepAliveMinTime,
387 GRPCKeepAliveInterval: DefaultGRPCKeepAliveInterval,
388 GRPCKeepAliveTimeout: DefaultGRPCKeepAliveTimeout,
389
390 TickMs: 100,
391 ElectionMs: 1000,
392 InitialElectionTickAdvance: true,
393
394 LPUrls: []url.URL{*lpurl},
395 LCUrls: []url.URL{*lcurl},
396 APUrls: []url.URL{*apurl},
397 ACUrls: []url.URL{*acurl},
398
399 ClusterState: ClusterStateFlagNew,
400 InitialClusterToken: "etcd-cluster",
401
402 StrictReconfigCheck: DefaultStrictReconfigCheck,
403 Metrics: "basic",
404 EnableV2: DefaultEnableV2,
405
406 CORS: map[string]struct{}{"*": {}},
407 HostWhitelist: map[string]struct{}{"*": {}},
408
409 AuthToken: "simple",
410 BcryptCost: uint(bcrypt.DefaultCost),
411
412 PreVote: false, // TODO: enable by default in v3.5
413
414 loggerMu: new(sync.RWMutex),
415 logger: nil,
416 Logger: "capnslog",
417 DeprecatedLogOutput: []string{DefaultLogOutput},
418 LogOutputs: []string{DefaultLogOutput},
419 Debug: false,
420 LogLevel: logutil.DefaultLogLevel,
421 LogPkgLevels: "",
422 }
423 cfg.InitialCluster = cfg.InitialClusterFromName(cfg.Name)
424 return cfg
425}
426
427func logTLSHandshakeFailure(conn *tls.Conn, err error) {
428 state := conn.ConnectionState()
429 remoteAddr := conn.RemoteAddr().String()
430 serverName := state.ServerName
431 if len(state.PeerCertificates) > 0 {
432 cert := state.PeerCertificates[0]
433 ips, dns := cert.IPAddresses, cert.DNSNames
434 plog.Infof("rejected connection from %q (error %q, ServerName %q, IPAddresses %q, DNSNames %q)", remoteAddr, err.Error(), serverName, ips, dns)
435 } else {
436 plog.Infof("rejected connection from %q (error %q, ServerName %q)", remoteAddr, err.Error(), serverName)
437 }
438}
439
440func ConfigFromFile(path string) (*Config, error) {
441 cfg := &configYAML{Config: *NewConfig()}
442 if err := cfg.configFromFile(path); err != nil {
443 return nil, err
444 }
445 return &cfg.Config, nil
446}
447
448func (cfg *configYAML) configFromFile(path string) error {
449 b, err := ioutil.ReadFile(path)
450 if err != nil {
451 return err
452 }
453
454 defaultInitialCluster := cfg.InitialCluster
455
456 err = yaml.Unmarshal(b, cfg)
457 if err != nil {
458 return err
459 }
460
461 if cfg.LPUrlsJSON != "" {
462 u, err := types.NewURLs(strings.Split(cfg.LPUrlsJSON, ","))
463 if err != nil {
464 fmt.Fprintf(os.Stderr, "unexpected error setting up listen-peer-urls: %v\n", err)
465 os.Exit(1)
466 }
467 cfg.LPUrls = []url.URL(u)
468 }
469
470 if cfg.LCUrlsJSON != "" {
471 u, err := types.NewURLs(strings.Split(cfg.LCUrlsJSON, ","))
472 if err != nil {
473 fmt.Fprintf(os.Stderr, "unexpected error setting up listen-client-urls: %v\n", err)
474 os.Exit(1)
475 }
476 cfg.LCUrls = []url.URL(u)
477 }
478
479 if cfg.APUrlsJSON != "" {
480 u, err := types.NewURLs(strings.Split(cfg.APUrlsJSON, ","))
481 if err != nil {
482 fmt.Fprintf(os.Stderr, "unexpected error setting up initial-advertise-peer-urls: %v\n", err)
483 os.Exit(1)
484 }
485 cfg.APUrls = []url.URL(u)
486 }
487
488 if cfg.ACUrlsJSON != "" {
489 u, err := types.NewURLs(strings.Split(cfg.ACUrlsJSON, ","))
490 if err != nil {
491 fmt.Fprintf(os.Stderr, "unexpected error setting up advertise-peer-urls: %v\n", err)
492 os.Exit(1)
493 }
494 cfg.ACUrls = []url.URL(u)
495 }
496
497 if cfg.ListenMetricsUrlsJSON != "" {
498 u, err := types.NewURLs(strings.Split(cfg.ListenMetricsUrlsJSON, ","))
499 if err != nil {
500 fmt.Fprintf(os.Stderr, "unexpected error setting up listen-metrics-urls: %v\n", err)
501 os.Exit(1)
502 }
503 cfg.ListenMetricsUrls = []url.URL(u)
504 }
505
506 if cfg.CORSJSON != "" {
507 uv := flags.NewUniqueURLsWithExceptions(cfg.CORSJSON, "*")
508 cfg.CORS = uv.Values
509 }
510
511 if cfg.HostWhitelistJSON != "" {
512 uv := flags.NewUniqueStringsValue(cfg.HostWhitelistJSON)
513 cfg.HostWhitelist = uv.Values
514 }
515
516 // If a discovery flag is set, clear default initial cluster set by InitialClusterFromName
517 if (cfg.Durl != "" || cfg.DNSCluster != "") && cfg.InitialCluster == defaultInitialCluster {
518 cfg.InitialCluster = ""
519 }
520 if cfg.ClusterState == "" {
521 cfg.ClusterState = ClusterStateFlagNew
522 }
523
524 copySecurityDetails := func(tls *transport.TLSInfo, ysc *securityConfig) {
525 tls.CertFile = ysc.CertFile
526 tls.KeyFile = ysc.KeyFile
527 tls.ClientCertAuth = ysc.CertAuth
528 tls.TrustedCAFile = ysc.TrustedCAFile
529 }
530 copySecurityDetails(&cfg.ClientTLSInfo, &cfg.ClientSecurityJSON)
531 copySecurityDetails(&cfg.PeerTLSInfo, &cfg.PeerSecurityJSON)
532 cfg.ClientAutoTLS = cfg.ClientSecurityJSON.AutoTLS
533 cfg.PeerAutoTLS = cfg.PeerSecurityJSON.AutoTLS
534
535 return cfg.Validate()
536}
537
538func updateCipherSuites(tls *transport.TLSInfo, ss []string) error {
539 if len(tls.CipherSuites) > 0 && len(ss) > 0 {
540 return fmt.Errorf("TLSInfo.CipherSuites is already specified (given %v)", ss)
541 }
542 if len(ss) > 0 {
543 cs := make([]uint16, len(ss))
544 for i, s := range ss {
545 var ok bool
546 cs[i], ok = tlsutil.GetCipherSuite(s)
547 if !ok {
548 return fmt.Errorf("unexpected TLS cipher suite %q", s)
549 }
550 }
551 tls.CipherSuites = cs
552 }
553 return nil
554}
555
556// Validate ensures that '*embed.Config' fields are properly configured.
557func (cfg *Config) Validate() error {
558 if err := cfg.setupLogging(); err != nil {
559 return err
560 }
561 if err := checkBindURLs(cfg.LPUrls); err != nil {
562 return err
563 }
564 if err := checkBindURLs(cfg.LCUrls); err != nil {
565 return err
566 }
567 if err := checkBindURLs(cfg.ListenMetricsUrls); err != nil {
568 return err
569 }
570 if err := checkHostURLs(cfg.APUrls); err != nil {
571 addrs := cfg.getAPURLs()
572 return fmt.Errorf(`--initial-advertise-peer-urls %q must be "host:port" (%v)`, strings.Join(addrs, ","), err)
573 }
574 if err := checkHostURLs(cfg.ACUrls); err != nil {
575 addrs := cfg.getACURLs()
576 return fmt.Errorf(`--advertise-client-urls %q must be "host:port" (%v)`, strings.Join(addrs, ","), err)
577 }
578 // Check if conflicting flags are passed.
579 nSet := 0
580 for _, v := range []bool{cfg.Durl != "", cfg.InitialCluster != "", cfg.DNSCluster != ""} {
581 if v {
582 nSet++
583 }
584 }
585
586 if cfg.ClusterState != ClusterStateFlagNew && cfg.ClusterState != ClusterStateFlagExisting {
587 return fmt.Errorf("unexpected clusterState %q", cfg.ClusterState)
588 }
589
590 if nSet > 1 {
591 return ErrConflictBootstrapFlags
592 }
593
594 if cfg.TickMs <= 0 {
595 return fmt.Errorf("--heartbeat-interval must be >0 (set to %dms)", cfg.TickMs)
596 }
597 if cfg.ElectionMs <= 0 {
598 return fmt.Errorf("--election-timeout must be >0 (set to %dms)", cfg.ElectionMs)
599 }
600 if 5*cfg.TickMs > cfg.ElectionMs {
601 return fmt.Errorf("--election-timeout[%vms] should be at least as 5 times as --heartbeat-interval[%vms]", cfg.ElectionMs, cfg.TickMs)
602 }
603 if cfg.ElectionMs > maxElectionMs {
604 return fmt.Errorf("--election-timeout[%vms] is too long, and should be set less than %vms", cfg.ElectionMs, maxElectionMs)
605 }
606
607 // check this last since proxying in etcdmain may make this OK
608 if cfg.LCUrls != nil && cfg.ACUrls == nil {
609 return ErrUnsetAdvertiseClientURLsFlag
610 }
611
612 switch cfg.AutoCompactionMode {
613 case "":
614 case CompactorModeRevision, CompactorModePeriodic:
615 default:
616 return fmt.Errorf("unknown auto-compaction-mode %q", cfg.AutoCompactionMode)
617 }
618
619 return nil
620}
621
622// PeerURLsMapAndToken sets up an initial peer URLsMap and cluster token for bootstrap or discovery.
623func (cfg *Config) PeerURLsMapAndToken(which string) (urlsmap types.URLsMap, token string, err error) {
624 token = cfg.InitialClusterToken
625 switch {
626 case cfg.Durl != "":
627 urlsmap = types.URLsMap{}
628 // If using discovery, generate a temporary cluster based on
629 // self's advertised peer URLs
630 urlsmap[cfg.Name] = cfg.APUrls
631 token = cfg.Durl
632
633 case cfg.DNSCluster != "":
634 clusterStrs, cerr := cfg.GetDNSClusterNames()
635 lg := cfg.logger
636 if cerr != nil {
637 if lg != nil {
638 lg.Warn("failed to resolve during SRV discovery", zap.Error(cerr))
639 } else {
640 plog.Errorf("couldn't resolve during SRV discovery (%v)", cerr)
641 }
642 return nil, "", cerr
643 }
644 for _, s := range clusterStrs {
645 if lg != nil {
646 lg.Info("got bootstrap from DNS for etcd-server", zap.String("node", s))
647 } else {
648 plog.Noticef("got bootstrap from DNS for etcd-server at %s", s)
649 }
650 }
651 clusterStr := strings.Join(clusterStrs, ",")
652 if strings.Contains(clusterStr, "https://") && cfg.PeerTLSInfo.TrustedCAFile == "" {
653 cfg.PeerTLSInfo.ServerName = cfg.DNSCluster
654 }
655 urlsmap, err = types.NewURLsMap(clusterStr)
656 // only etcd member must belong to the discovered cluster.
657 // proxy does not need to belong to the discovered cluster.
658 if which == "etcd" {
659 if _, ok := urlsmap[cfg.Name]; !ok {
660 return nil, "", fmt.Errorf("cannot find local etcd member %q in SRV records", cfg.Name)
661 }
662 }
663
664 default:
665 // We're statically configured, and cluster has appropriately been set.
666 urlsmap, err = types.NewURLsMap(cfg.InitialCluster)
667 }
668 return urlsmap, token, err
669}
670
671// GetDNSClusterNames uses DNS SRV records to get a list of initial nodes for cluster bootstrapping.
672func (cfg *Config) GetDNSClusterNames() ([]string, error) {
673 var (
674 clusterStrs []string
675 cerr error
676 serviceNameSuffix string
677 )
678 if cfg.DNSClusterServiceName != "" {
679 serviceNameSuffix = "-" + cfg.DNSClusterServiceName
680 }
681
682 lg := cfg.GetLogger()
683
684 // Use both etcd-server-ssl and etcd-server for discovery.
685 // Combine the results if both are available.
686 clusterStrs, cerr = srv.GetCluster("https", "etcd-server-ssl"+serviceNameSuffix, cfg.Name, cfg.DNSCluster, cfg.APUrls)
687 if cerr != nil {
688 clusterStrs = make([]string, 0)
689 }
690 if lg != nil {
691 lg.Info(
692 "get cluster for etcd-server-ssl SRV",
693 zap.String("service-scheme", "https"),
694 zap.String("service-name", "etcd-server-ssl"+serviceNameSuffix),
695 zap.String("server-name", cfg.Name),
696 zap.String("discovery-srv", cfg.DNSCluster),
697 zap.Strings("advertise-peer-urls", cfg.getAPURLs()),
698 zap.Strings("found-cluster", clusterStrs),
699 zap.Error(cerr),
700 )
701 }
702
703 defaultHTTPClusterStrs, httpCerr := srv.GetCluster("http", "etcd-server"+serviceNameSuffix, cfg.Name, cfg.DNSCluster, cfg.APUrls)
704 if httpCerr != nil {
705 clusterStrs = append(clusterStrs, defaultHTTPClusterStrs...)
706 }
707 if lg != nil {
708 lg.Info(
709 "get cluster for etcd-server SRV",
710 zap.String("service-scheme", "http"),
711 zap.String("service-name", "etcd-server"+serviceNameSuffix),
712 zap.String("server-name", cfg.Name),
713 zap.String("discovery-srv", cfg.DNSCluster),
714 zap.Strings("advertise-peer-urls", cfg.getAPURLs()),
715 zap.Strings("found-cluster", clusterStrs),
716 zap.Error(httpCerr),
717 )
718 }
719
720 return clusterStrs, cerr
721}
722
723func (cfg Config) InitialClusterFromName(name string) (ret string) {
724 if len(cfg.APUrls) == 0 {
725 return ""
726 }
727 n := name
728 if name == "" {
729 n = DefaultName
730 }
731 for i := range cfg.APUrls {
732 ret = ret + "," + n + "=" + cfg.APUrls[i].String()
733 }
734 return ret[1:]
735}
736
737func (cfg Config) IsNewCluster() bool { return cfg.ClusterState == ClusterStateFlagNew }
738func (cfg Config) ElectionTicks() int { return int(cfg.ElectionMs / cfg.TickMs) }
739
740func (cfg Config) defaultPeerHost() bool {
741 return len(cfg.APUrls) == 1 && cfg.APUrls[0].String() == DefaultInitialAdvertisePeerURLs
742}
743
744func (cfg Config) defaultClientHost() bool {
745 return len(cfg.ACUrls) == 1 && cfg.ACUrls[0].String() == DefaultAdvertiseClientURLs
746}
747
748func (cfg *Config) ClientSelfCert() (err error) {
749 if !cfg.ClientAutoTLS {
750 return nil
751 }
752 if !cfg.ClientTLSInfo.Empty() {
753 if cfg.logger != nil {
754 cfg.logger.Warn("ignoring client auto TLS since certs given")
755 } else {
756 plog.Warningf("ignoring client auto TLS since certs given")
757 }
758 return nil
759 }
760 chosts := make([]string, len(cfg.LCUrls))
761 for i, u := range cfg.LCUrls {
762 chosts[i] = u.Host
763 }
764 cfg.ClientTLSInfo, err = transport.SelfCert(cfg.logger, filepath.Join(cfg.Dir, "fixtures", "client"), chosts)
765 if err != nil {
766 return err
767 }
768 return updateCipherSuites(&cfg.ClientTLSInfo, cfg.CipherSuites)
769}
770
771func (cfg *Config) PeerSelfCert() (err error) {
772 if !cfg.PeerAutoTLS {
773 return nil
774 }
775 if !cfg.PeerTLSInfo.Empty() {
776 if cfg.logger != nil {
777 cfg.logger.Warn("ignoring peer auto TLS since certs given")
778 } else {
779 plog.Warningf("ignoring peer auto TLS since certs given")
780 }
781 return nil
782 }
783 phosts := make([]string, len(cfg.LPUrls))
784 for i, u := range cfg.LPUrls {
785 phosts[i] = u.Host
786 }
787 cfg.PeerTLSInfo, err = transport.SelfCert(cfg.logger, filepath.Join(cfg.Dir, "fixtures", "peer"), phosts)
788 if err != nil {
789 return err
790 }
791 return updateCipherSuites(&cfg.PeerTLSInfo, cfg.CipherSuites)
792}
793
794// UpdateDefaultClusterFromName updates cluster advertise URLs with, if available, default host,
795// if advertise URLs are default values(localhost:2379,2380) AND if listen URL is 0.0.0.0.
796// e.g. advertise peer URL localhost:2380 or listen peer URL 0.0.0.0:2380
797// then the advertise peer host would be updated with machine's default host,
798// while keeping the listen URL's port.
799// User can work around this by explicitly setting URL with 127.0.0.1.
800// It returns the default hostname, if used, and the error, if any, from getting the machine's default host.
801// TODO: check whether fields are set instead of whether fields have default value
802func (cfg *Config) UpdateDefaultClusterFromName(defaultInitialCluster string) (string, error) {
803 if defaultHostname == "" || defaultHostStatus != nil {
804 // update 'initial-cluster' when only the name is specified (e.g. 'etcd --name=abc')
805 if cfg.Name != DefaultName && cfg.InitialCluster == defaultInitialCluster {
806 cfg.InitialCluster = cfg.InitialClusterFromName(cfg.Name)
807 }
808 return "", defaultHostStatus
809 }
810
811 used := false
812 pip, pport := cfg.LPUrls[0].Hostname(), cfg.LPUrls[0].Port()
813 if cfg.defaultPeerHost() && pip == "0.0.0.0" {
814 cfg.APUrls[0] = url.URL{Scheme: cfg.APUrls[0].Scheme, Host: fmt.Sprintf("%s:%s", defaultHostname, pport)}
815 used = true
816 }
817 // update 'initial-cluster' when only the name is specified (e.g. 'etcd --name=abc')
818 if cfg.Name != DefaultName && cfg.InitialCluster == defaultInitialCluster {
819 cfg.InitialCluster = cfg.InitialClusterFromName(cfg.Name)
820 }
821
822 cip, cport := cfg.LCUrls[0].Hostname(), cfg.LCUrls[0].Port()
823 if cfg.defaultClientHost() && cip == "0.0.0.0" {
824 cfg.ACUrls[0] = url.URL{Scheme: cfg.ACUrls[0].Scheme, Host: fmt.Sprintf("%s:%s", defaultHostname, cport)}
825 used = true
826 }
827 dhost := defaultHostname
828 if !used {
829 dhost = ""
830 }
831 return dhost, defaultHostStatus
832}
833
834// checkBindURLs returns an error if any URL uses a domain name.
835func checkBindURLs(urls []url.URL) error {
836 for _, url := range urls {
837 if url.Scheme == "unix" || url.Scheme == "unixs" {
838 continue
839 }
840 host, _, err := net.SplitHostPort(url.Host)
841 if err != nil {
842 return err
843 }
844 if host == "localhost" {
845 // special case for local address
846 // TODO: support /etc/hosts ?
847 continue
848 }
849 if net.ParseIP(host) == nil {
850 return fmt.Errorf("expected IP in URL for binding (%s)", url.String())
851 }
852 }
853 return nil
854}
855
856func checkHostURLs(urls []url.URL) error {
857 for _, url := range urls {
858 host, _, err := net.SplitHostPort(url.Host)
859 if err != nil {
860 return err
861 }
862 if host == "" {
863 return fmt.Errorf("unexpected empty host (%s)", url.String())
864 }
865 }
866 return nil
867}
868
869func (cfg *Config) getAPURLs() (ss []string) {
870 ss = make([]string, len(cfg.APUrls))
871 for i := range cfg.APUrls {
872 ss[i] = cfg.APUrls[i].String()
873 }
874 return ss
875}
876
877func (cfg *Config) getLPURLs() (ss []string) {
878 ss = make([]string, len(cfg.LPUrls))
879 for i := range cfg.LPUrls {
880 ss[i] = cfg.LPUrls[i].String()
881 }
882 return ss
883}
884
885func (cfg *Config) getACURLs() (ss []string) {
886 ss = make([]string, len(cfg.ACUrls))
887 for i := range cfg.ACUrls {
888 ss[i] = cfg.ACUrls[i].String()
889 }
890 return ss
891}
892
893func (cfg *Config) getLCURLs() (ss []string) {
894 ss = make([]string, len(cfg.LCUrls))
895 for i := range cfg.LCUrls {
896 ss[i] = cfg.LCUrls[i].String()
897 }
898 return ss
899}
900
901func (cfg *Config) getMetricsURLs() (ss []string) {
902 ss = make([]string, len(cfg.ListenMetricsUrls))
903 for i := range cfg.ListenMetricsUrls {
904 ss[i] = cfg.ListenMetricsUrls[i].String()
905 }
906 return ss
907}
908
909func parseBackendFreelistType(freelistType string) bolt.FreelistType {
910 if freelistType == freelistMapType {
911 return bolt.FreelistMapType
912 }
913
914 return bolt.FreelistArrayType
915}