go.etcd.io/etcd@v3.3.27+incompatible/embed/config.go (about)

     1  // 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  
    15  package embed
    16  
    17  import (
    18  	"crypto/tls"
    19  	"fmt"
    20  	"io/ioutil"
    21  	"net"
    22  	"net/http"
    23  	"net/url"
    24  	"os"
    25  	"path/filepath"
    26  	"strings"
    27  	"time"
    28  
    29  	"github.com/coreos/etcd/compactor"
    30  	"github.com/coreos/etcd/etcdserver"
    31  	"github.com/coreos/etcd/pkg/cors"
    32  	"github.com/coreos/etcd/pkg/netutil"
    33  	"github.com/coreos/etcd/pkg/srv"
    34  	"github.com/coreos/etcd/pkg/tlsutil"
    35  	"github.com/coreos/etcd/pkg/transport"
    36  	"github.com/coreos/etcd/pkg/types"
    37  
    38  	"github.com/coreos/pkg/capnslog"
    39  	"google.golang.org/grpc"
    40  	"google.golang.org/grpc/grpclog"
    41  	"sigs.k8s.io/yaml"
    42  )
    43  
    44  const (
    45  	ClusterStateFlagNew      = "new"
    46  	ClusterStateFlagExisting = "existing"
    47  
    48  	DefaultName                  = "default"
    49  	DefaultMaxSnapshots          = 5
    50  	DefaultMaxWALs               = 5
    51  	DefaultMaxTxnOps             = uint(128)
    52  	DefaultMaxRequestBytes       = 1.5 * 1024 * 1024
    53  	DefaultGRPCKeepAliveMinTime  = 5 * time.Second
    54  	DefaultGRPCKeepAliveInterval = 2 * time.Hour
    55  	DefaultGRPCKeepAliveTimeout  = 20 * time.Second
    56  
    57  	DefaultListenPeerURLs   = "http://localhost:2380"
    58  	DefaultListenClientURLs = "http://localhost:2379"
    59  
    60  	DefaultLogOutput = "default"
    61  
    62  	// DefaultStrictReconfigCheck is the default value for "--strict-reconfig-check" flag.
    63  	// It's enabled by default.
    64  	DefaultStrictReconfigCheck = true
    65  	// DefaultEnableV2 is the default value for "--enable-v2" flag.
    66  	// v2 is enabled by default.
    67  	// TODO: disable v2 when deprecated.
    68  	DefaultEnableV2 = true
    69  
    70  	// maxElectionMs specifies the maximum value of election timeout.
    71  	// More details are listed in ../Documentation/tuning.md#time-parameters.
    72  	maxElectionMs = 50000
    73  )
    74  
    75  var (
    76  	ErrConflictBootstrapFlags = fmt.Errorf("multiple discovery or bootstrap flags are set. " +
    77  		"Choose one of \"initial-cluster\", \"discovery\" or \"discovery-srv\"")
    78  	ErrUnsetAdvertiseClientURLsFlag = fmt.Errorf("--advertise-client-urls is required when --listen-client-urls is set explicitly")
    79  
    80  	DefaultInitialAdvertisePeerURLs = "http://localhost:2380"
    81  	DefaultAdvertiseClientURLs      = "http://localhost:2379"
    82  
    83  	defaultHostname   string
    84  	defaultHostStatus error
    85  )
    86  
    87  func init() {
    88  	defaultHostname, defaultHostStatus = netutil.GetDefaultHost()
    89  }
    90  
    91  // Config holds the arguments for configuring an etcd server.
    92  type Config struct {
    93  	// member
    94  
    95  	CorsInfo       *cors.CORSInfo
    96  	LPUrls, LCUrls []url.URL
    97  	Dir            string `json:"data-dir"`
    98  	WalDir         string `json:"wal-dir"`
    99  	MaxSnapFiles   uint   `json:"max-snapshots"`
   100  	MaxWalFiles    uint   `json:"max-wals"`
   101  	Name           string `json:"name"`
   102  	SnapCount      uint64 `json:"snapshot-count"`
   103  
   104  	// AutoCompactionMode is either 'periodic' or 'revision'.
   105  	AutoCompactionMode string `json:"auto-compaction-mode"`
   106  	// AutoCompactionRetention is either duration string with time unit
   107  	// (e.g. '5m' for 5-minute), or revision unit (e.g. '5000').
   108  	// If no time unit is provided and compaction mode is 'periodic',
   109  	// the unit defaults to hour. For example, '5' translates into 5-hour.
   110  	AutoCompactionRetention string `json:"auto-compaction-retention"`
   111  
   112  	// TickMs is the number of milliseconds between heartbeat ticks.
   113  	// TODO: decouple tickMs and heartbeat tick (current heartbeat tick = 1).
   114  	// make ticks a cluster wide configuration.
   115  	TickMs     uint `json:"heartbeat-interval"`
   116  	ElectionMs uint `json:"election-timeout"`
   117  
   118  	// InitialElectionTickAdvance is true, then local member fast-forwards
   119  	// election ticks to speed up "initial" leader election trigger. This
   120  	// benefits the case of larger election ticks. For instance, cross
   121  	// datacenter deployment may require longer election timeout of 10-second.
   122  	// If true, local node does not need wait up to 10-second. Instead,
   123  	// forwards its election ticks to 8-second, and have only 2-second left
   124  	// before leader election.
   125  	//
   126  	// Major assumptions are that:
   127  	//  - cluster has no active leader thus advancing ticks enables faster
   128  	//    leader election, or
   129  	//  - cluster already has an established leader, and rejoining follower
   130  	//    is likely to receive heartbeats from the leader after tick advance
   131  	//    and before election timeout.
   132  	//
   133  	// However, when network from leader to rejoining follower is congested,
   134  	// and the follower does not receive leader heartbeat within left election
   135  	// ticks, disruptive election has to happen thus affecting cluster
   136  	// availabilities.
   137  	//
   138  	// Disabling this would slow down initial bootstrap process for cross
   139  	// datacenter deployments. Make your own tradeoffs by configuring
   140  	// --initial-election-tick-advance at the cost of slow initial bootstrap.
   141  	//
   142  	// If single-node, it advances ticks regardless.
   143  	//
   144  	// See https://github.com/coreos/etcd/issues/9333 for more detail.
   145  	InitialElectionTickAdvance bool `json:"initial-election-tick-advance"`
   146  
   147  	QuotaBackendBytes int64 `json:"quota-backend-bytes"`
   148  	MaxTxnOps         uint  `json:"max-txn-ops"`
   149  	MaxRequestBytes   uint  `json:"max-request-bytes"`
   150  
   151  	// gRPC server options
   152  
   153  	// GRPCKeepAliveMinTime is the minimum interval that a client should
   154  	// wait before pinging server. When client pings "too fast", server
   155  	// sends goaway and closes the connection (errors: too_many_pings,
   156  	// http2.ErrCodeEnhanceYourCalm). When too slow, nothing happens.
   157  	// Server expects client pings only when there is any active streams
   158  	// (PermitWithoutStream is set false).
   159  	GRPCKeepAliveMinTime time.Duration `json:"grpc-keepalive-min-time"`
   160  	// GRPCKeepAliveInterval is the frequency of server-to-client ping
   161  	// to check if a connection is alive. Close a non-responsive connection
   162  	// after an additional duration of Timeout. 0 to disable.
   163  	GRPCKeepAliveInterval time.Duration `json:"grpc-keepalive-interval"`
   164  	// GRPCKeepAliveTimeout is the additional duration of wait
   165  	// before closing a non-responsive connection. 0 to disable.
   166  	GRPCKeepAliveTimeout time.Duration `json:"grpc-keepalive-timeout"`
   167  
   168  	// clustering
   169  
   170  	APUrls, ACUrls      []url.URL
   171  	ClusterState        string `json:"initial-cluster-state"`
   172  	DNSCluster          string `json:"discovery-srv"`
   173  	Dproxy              string `json:"discovery-proxy"`
   174  	Durl                string `json:"discovery"`
   175  	InitialCluster      string `json:"initial-cluster"`
   176  	InitialClusterToken string `json:"initial-cluster-token"`
   177  	StrictReconfigCheck bool   `json:"strict-reconfig-check"`
   178  	EnableV2            bool   `json:"enable-v2"`
   179  
   180  	// security
   181  
   182  	ClientTLSInfo transport.TLSInfo
   183  	ClientAutoTLS bool
   184  	PeerTLSInfo   transport.TLSInfo
   185  	PeerAutoTLS   bool
   186  
   187  	// CipherSuites is a list of supported TLS cipher suites between
   188  	// client/server and peers. If empty, Go auto-populates the list.
   189  	// Note that cipher suites are prioritized in the given order.
   190  	CipherSuites []string `json:"cipher-suites"`
   191  
   192  	// debug
   193  
   194  	Debug                 bool   `json:"debug"`
   195  	LogPkgLevels          string `json:"log-package-levels"`
   196  	LogOutput             string `json:"log-output"`
   197  	EnablePprof           bool   `json:"enable-pprof"`
   198  	Metrics               string `json:"metrics"`
   199  	ListenMetricsUrls     []url.URL
   200  	ListenMetricsUrlsJSON string `json:"listen-metrics-urls"`
   201  
   202  	// ForceNewCluster starts a new cluster even if previously started; unsafe.
   203  	ForceNewCluster bool `json:"force-new-cluster"`
   204  
   205  	// UserHandlers is for registering users handlers and only used for
   206  	// embedding etcd into other applications.
   207  	// The map key is the route path for the handler, and
   208  	// you must ensure it can't be conflicted with etcd's.
   209  	UserHandlers map[string]http.Handler `json:"-"`
   210  	// ServiceRegister is for registering users' gRPC services. A simple usage example:
   211  	//	cfg := embed.NewConfig()
   212  	//	cfg.ServerRegister = func(s *grpc.Server) {
   213  	//		pb.RegisterFooServer(s, &fooServer{})
   214  	//		pb.RegisterBarServer(s, &barServer{})
   215  	//	}
   216  	//	embed.StartEtcd(cfg)
   217  	ServiceRegister func(*grpc.Server) `json:"-"`
   218  
   219  	// auth
   220  
   221  	AuthToken string `json:"auth-token"`
   222  
   223  	// Experimental flags
   224  
   225  	//The AuthTokenTTL in seconds of the simple token
   226  	AuthTokenTTL uint `json:"auth-token-ttl"`
   227  
   228  	ExperimentalInitialCorruptCheck bool          `json:"experimental-initial-corrupt-check"`
   229  	ExperimentalCorruptCheckTime    time.Duration `json:"experimental-corrupt-check-time"`
   230  	ExperimentalEnableV2V3          string        `json:"experimental-enable-v2v3"`
   231  }
   232  
   233  // configYAML holds the config suitable for yaml parsing
   234  type configYAML struct {
   235  	Config
   236  	configJSON
   237  }
   238  
   239  // configJSON has file options that are translated into Config options
   240  type configJSON struct {
   241  	LPUrlsJSON         string         `json:"listen-peer-urls"`
   242  	LCUrlsJSON         string         `json:"listen-client-urls"`
   243  	CorsJSON           string         `json:"cors"`
   244  	APUrlsJSON         string         `json:"initial-advertise-peer-urls"`
   245  	ACUrlsJSON         string         `json:"advertise-client-urls"`
   246  	ClientSecurityJSON securityConfig `json:"client-transport-security"`
   247  	PeerSecurityJSON   securityConfig `json:"peer-transport-security"`
   248  }
   249  
   250  type securityConfig struct {
   251  	CAFile        string `json:"ca-file"`
   252  	CertFile      string `json:"cert-file"`
   253  	KeyFile       string `json:"key-file"`
   254  	CertAuth      bool   `json:"client-cert-auth"`
   255  	TrustedCAFile string `json:"trusted-ca-file"`
   256  	AutoTLS       bool   `json:"auto-tls"`
   257  }
   258  
   259  // NewConfig creates a new Config populated with default values.
   260  func NewConfig() *Config {
   261  	lpurl, _ := url.Parse(DefaultListenPeerURLs)
   262  	apurl, _ := url.Parse(DefaultInitialAdvertisePeerURLs)
   263  	lcurl, _ := url.Parse(DefaultListenClientURLs)
   264  	acurl, _ := url.Parse(DefaultAdvertiseClientURLs)
   265  	cfg := &Config{
   266  		CorsInfo:                   &cors.CORSInfo{},
   267  		MaxSnapFiles:               DefaultMaxSnapshots,
   268  		MaxWalFiles:                DefaultMaxWALs,
   269  		Name:                       DefaultName,
   270  		SnapCount:                  etcdserver.DefaultSnapCount,
   271  		MaxTxnOps:                  DefaultMaxTxnOps,
   272  		MaxRequestBytes:            DefaultMaxRequestBytes,
   273  		GRPCKeepAliveMinTime:       DefaultGRPCKeepAliveMinTime,
   274  		GRPCKeepAliveInterval:      DefaultGRPCKeepAliveInterval,
   275  		GRPCKeepAliveTimeout:       DefaultGRPCKeepAliveTimeout,
   276  		TickMs:                     100,
   277  		ElectionMs:                 1000,
   278  		InitialElectionTickAdvance: true,
   279  		LPUrls:                     []url.URL{*lpurl},
   280  		LCUrls:                     []url.URL{*lcurl},
   281  		APUrls:                     []url.URL{*apurl},
   282  		ACUrls:                     []url.URL{*acurl},
   283  		ClusterState:               ClusterStateFlagNew,
   284  		InitialClusterToken:        "etcd-cluster",
   285  		StrictReconfigCheck:        DefaultStrictReconfigCheck,
   286  		LogOutput:                  DefaultLogOutput,
   287  		Metrics:                    "basic",
   288  		EnableV2:                   DefaultEnableV2,
   289  		AuthToken:                  "simple",
   290  		AuthTokenTTL: 300,
   291  	}
   292  	cfg.InitialCluster = cfg.InitialClusterFromName(cfg.Name)
   293  	return cfg
   294  }
   295  
   296  func logTLSHandshakeFailure(conn *tls.Conn, err error) {
   297  	state := conn.ConnectionState()
   298  	remoteAddr := conn.RemoteAddr().String()
   299  	serverName := state.ServerName
   300  	if len(state.PeerCertificates) > 0 {
   301  		cert := state.PeerCertificates[0]
   302  		ips, dns := cert.IPAddresses, cert.DNSNames
   303  		plog.Infof("rejected connection from %q (error %q, ServerName %q, IPAddresses %q, DNSNames %q)", remoteAddr, err.Error(), serverName, ips, dns)
   304  	} else {
   305  		plog.Infof("rejected connection from %q (error %q, ServerName %q)", remoteAddr, err.Error(), serverName)
   306  	}
   307  }
   308  
   309  // SetupLogging initializes etcd logging.
   310  // Must be called after flag parsing.
   311  func (cfg *Config) SetupLogging() {
   312  	cfg.ClientTLSInfo.HandshakeFailure = logTLSHandshakeFailure
   313  	cfg.PeerTLSInfo.HandshakeFailure = logTLSHandshakeFailure
   314  
   315  	capnslog.SetGlobalLogLevel(capnslog.INFO)
   316  	if cfg.Debug {
   317  		capnslog.SetGlobalLogLevel(capnslog.DEBUG)
   318  		grpc.EnableTracing = true
   319  		// enable info, warning, error
   320  		grpclog.SetLoggerV2(grpclog.NewLoggerV2(os.Stderr, os.Stderr, os.Stderr))
   321  	} else {
   322  		// only discard info
   323  		grpclog.SetLoggerV2(grpclog.NewLoggerV2(ioutil.Discard, os.Stderr, os.Stderr))
   324  	}
   325  	if cfg.LogPkgLevels != "" {
   326  		repoLog := capnslog.MustRepoLogger("github.com/coreos/etcd")
   327  		settings, err := repoLog.ParseLogLevelConfig(cfg.LogPkgLevels)
   328  		if err != nil {
   329  			plog.Warningf("couldn't parse log level string: %s, continuing with default levels", err.Error())
   330  			return
   331  		}
   332  		repoLog.SetLogLevel(settings)
   333  	}
   334  
   335  	// capnslog initially SetFormatter(NewDefaultFormatter(os.Stderr))
   336  	// where NewDefaultFormatter returns NewJournaldFormatter when syscall.Getppid() == 1
   337  	// specify 'stdout' or 'stderr' to skip journald logging even when running under systemd
   338  	switch cfg.LogOutput {
   339  	case "stdout":
   340  		capnslog.SetFormatter(capnslog.NewPrettyFormatter(os.Stdout, cfg.Debug))
   341  	case "stderr":
   342  		capnslog.SetFormatter(capnslog.NewPrettyFormatter(os.Stderr, cfg.Debug))
   343  	case DefaultLogOutput:
   344  	default:
   345  		plog.Panicf(`unknown log-output %q (only supports %q, "stdout", "stderr")`, cfg.LogOutput, DefaultLogOutput)
   346  	}
   347  }
   348  
   349  func ConfigFromFile(path string) (*Config, error) {
   350  	cfg := &configYAML{Config: *NewConfig()}
   351  	if err := cfg.configFromFile(path); err != nil {
   352  		return nil, err
   353  	}
   354  	return &cfg.Config, nil
   355  }
   356  
   357  func (cfg *configYAML) configFromFile(path string) error {
   358  	b, err := ioutil.ReadFile(path)
   359  	if err != nil {
   360  		return err
   361  	}
   362  
   363  	defaultInitialCluster := cfg.InitialCluster
   364  
   365  	err = yaml.Unmarshal(b, cfg)
   366  	if err != nil {
   367  		return err
   368  	}
   369  
   370  	if cfg.LPUrlsJSON != "" {
   371  		u, err := types.NewURLs(strings.Split(cfg.LPUrlsJSON, ","))
   372  		if err != nil {
   373  			plog.Fatalf("unexpected error setting up listen-peer-urls: %v", err)
   374  		}
   375  		cfg.LPUrls = []url.URL(u)
   376  	}
   377  
   378  	if cfg.LCUrlsJSON != "" {
   379  		u, err := types.NewURLs(strings.Split(cfg.LCUrlsJSON, ","))
   380  		if err != nil {
   381  			plog.Fatalf("unexpected error setting up listen-client-urls: %v", err)
   382  		}
   383  		cfg.LCUrls = []url.URL(u)
   384  	}
   385  
   386  	if cfg.CorsJSON != "" {
   387  		if err := cfg.CorsInfo.Set(cfg.CorsJSON); err != nil {
   388  			plog.Panicf("unexpected error setting up cors: %v", err)
   389  		}
   390  	}
   391  
   392  	if cfg.APUrlsJSON != "" {
   393  		u, err := types.NewURLs(strings.Split(cfg.APUrlsJSON, ","))
   394  		if err != nil {
   395  			plog.Fatalf("unexpected error setting up initial-advertise-peer-urls: %v", err)
   396  		}
   397  		cfg.APUrls = []url.URL(u)
   398  	}
   399  
   400  	if cfg.ACUrlsJSON != "" {
   401  		u, err := types.NewURLs(strings.Split(cfg.ACUrlsJSON, ","))
   402  		if err != nil {
   403  			plog.Fatalf("unexpected error setting up advertise-peer-urls: %v", err)
   404  		}
   405  		cfg.ACUrls = []url.URL(u)
   406  	}
   407  
   408  	if cfg.ListenMetricsUrlsJSON != "" {
   409  		u, err := types.NewURLs(strings.Split(cfg.ListenMetricsUrlsJSON, ","))
   410  		if err != nil {
   411  			plog.Fatalf("unexpected error setting up listen-metrics-urls: %v", err)
   412  		}
   413  		cfg.ListenMetricsUrls = []url.URL(u)
   414  	}
   415  
   416  	// If a discovery flag is set, clear default initial cluster set by InitialClusterFromName
   417  	if (cfg.Durl != "" || cfg.DNSCluster != "") && cfg.InitialCluster == defaultInitialCluster {
   418  		cfg.InitialCluster = ""
   419  	}
   420  	if cfg.ClusterState == "" {
   421  		cfg.ClusterState = ClusterStateFlagNew
   422  	}
   423  
   424  	copySecurityDetails := func(tls *transport.TLSInfo, ysc *securityConfig) {
   425  		tls.CAFile = ysc.CAFile
   426  		tls.CertFile = ysc.CertFile
   427  		tls.KeyFile = ysc.KeyFile
   428  		tls.ClientCertAuth = ysc.CertAuth
   429  		tls.TrustedCAFile = ysc.TrustedCAFile
   430  	}
   431  	copySecurityDetails(&cfg.ClientTLSInfo, &cfg.ClientSecurityJSON)
   432  	copySecurityDetails(&cfg.PeerTLSInfo, &cfg.PeerSecurityJSON)
   433  	cfg.ClientAutoTLS = cfg.ClientSecurityJSON.AutoTLS
   434  	cfg.PeerAutoTLS = cfg.PeerSecurityJSON.AutoTLS
   435  
   436  	return cfg.Validate()
   437  }
   438  
   439  func updateCipherSuites(tls *transport.TLSInfo, ss []string) error {
   440  	if len(tls.CipherSuites) > 0 && len(ss) > 0 {
   441  		return fmt.Errorf("TLSInfo.CipherSuites is already specified (given %v)", ss)
   442  	}
   443  	if len(ss) > 0 {
   444  		cs := make([]uint16, len(ss))
   445  		for i, s := range ss {
   446  			var ok bool
   447  			cs[i], ok = tlsutil.GetCipherSuite(s)
   448  			if !ok {
   449  				return fmt.Errorf("unexpected TLS cipher suite %q", s)
   450  			}
   451  		}
   452  		tls.CipherSuites = cs
   453  	}
   454  	return nil
   455  }
   456  
   457  // Validate ensures that '*embed.Config' fields are properly configured.
   458  func (cfg *Config) Validate() error {
   459  	if err := checkBindURLs(cfg.LPUrls); err != nil {
   460  		return err
   461  	}
   462  	if err := checkBindURLs(cfg.LCUrls); err != nil {
   463  		return err
   464  	}
   465  	if err := checkBindURLs(cfg.ListenMetricsUrls); err != nil {
   466  		return err
   467  	}
   468  	if err := checkHostURLs(cfg.APUrls); err != nil {
   469  		// TODO: return err in v3.4
   470  		addrs := make([]string, len(cfg.APUrls))
   471  		for i := range cfg.APUrls {
   472  			addrs[i] = cfg.APUrls[i].String()
   473  		}
   474  		plog.Warningf("advertise-peer-urls %q is deprecated (%v)", strings.Join(addrs, ","), err)
   475  	}
   476  	if err := checkHostURLs(cfg.ACUrls); err != nil {
   477  		// TODO: return err in v3.4
   478  		addrs := make([]string, len(cfg.ACUrls))
   479  		for i := range cfg.ACUrls {
   480  			addrs[i] = cfg.ACUrls[i].String()
   481  		}
   482  		plog.Warningf("advertise-client-urls %q is deprecated (%v)", strings.Join(addrs, ","), err)
   483  	}
   484  
   485  	// Check if conflicting flags are passed.
   486  	nSet := 0
   487  	for _, v := range []bool{cfg.Durl != "", cfg.InitialCluster != "", cfg.DNSCluster != ""} {
   488  		if v {
   489  			nSet++
   490  		}
   491  	}
   492  
   493  	if cfg.ClusterState != ClusterStateFlagNew && cfg.ClusterState != ClusterStateFlagExisting {
   494  		return fmt.Errorf("unexpected clusterState %q", cfg.ClusterState)
   495  	}
   496  
   497  	if nSet > 1 {
   498  		return ErrConflictBootstrapFlags
   499  	}
   500  
   501  	if cfg.TickMs <= 0 {
   502  		return fmt.Errorf("--heartbeat-interval must be >0 (set to %dms)", cfg.TickMs)
   503  	}
   504  	if cfg.ElectionMs <= 0 {
   505  		return fmt.Errorf("--election-timeout must be >0 (set to %dms)", cfg.ElectionMs)
   506  	}
   507  	if 5*cfg.TickMs > cfg.ElectionMs {
   508  		return fmt.Errorf("--election-timeout[%vms] should be at least as 5 times as --heartbeat-interval[%vms]", cfg.ElectionMs, cfg.TickMs)
   509  	}
   510  	if cfg.ElectionMs > maxElectionMs {
   511  		return fmt.Errorf("--election-timeout[%vms] is too long, and should be set less than %vms", cfg.ElectionMs, maxElectionMs)
   512  	}
   513  
   514  	// check this last since proxying in etcdmain may make this OK
   515  	if cfg.LCUrls != nil && cfg.ACUrls == nil {
   516  		return ErrUnsetAdvertiseClientURLsFlag
   517  	}
   518  
   519  	switch cfg.AutoCompactionMode {
   520  	case "":
   521  	case compactor.ModeRevision, compactor.ModePeriodic:
   522  	default:
   523  		return fmt.Errorf("unknown auto-compaction-mode %q", cfg.AutoCompactionMode)
   524  	}
   525  
   526  	return nil
   527  }
   528  
   529  // PeerURLsMapAndToken sets up an initial peer URLsMap and cluster token for bootstrap or discovery.
   530  func (cfg *Config) PeerURLsMapAndToken(which string) (urlsmap types.URLsMap, token string, err error) {
   531  	token = cfg.InitialClusterToken
   532  	switch {
   533  	case cfg.Durl != "":
   534  		urlsmap = types.URLsMap{}
   535  		// If using discovery, generate a temporary cluster based on
   536  		// self's advertised peer URLs
   537  		urlsmap[cfg.Name] = cfg.APUrls
   538  		token = cfg.Durl
   539  	case cfg.DNSCluster != "":
   540  		clusterStrs, cerr := srv.GetCluster("etcd-server", cfg.Name, cfg.DNSCluster, cfg.APUrls)
   541  		if cerr != nil {
   542  			plog.Errorf("couldn't resolve during SRV discovery (%v)", cerr)
   543  			return nil, "", cerr
   544  		}
   545  		for _, s := range clusterStrs {
   546  			plog.Noticef("got bootstrap from DNS for etcd-server at %s", s)
   547  		}
   548  		clusterStr := strings.Join(clusterStrs, ",")
   549  		if strings.Contains(clusterStr, "https://") && cfg.PeerTLSInfo.CAFile == "" {
   550  			cfg.PeerTLSInfo.ServerName = cfg.DNSCluster
   551  		}
   552  		urlsmap, err = types.NewURLsMap(clusterStr)
   553  		// only etcd member must belong to the discovered cluster.
   554  		// proxy does not need to belong to the discovered cluster.
   555  		if which == "etcd" {
   556  			if _, ok := urlsmap[cfg.Name]; !ok {
   557  				return nil, "", fmt.Errorf("cannot find local etcd member %q in SRV records", cfg.Name)
   558  			}
   559  		}
   560  	default:
   561  		// We're statically configured, and cluster has appropriately been set.
   562  		urlsmap, err = types.NewURLsMap(cfg.InitialCluster)
   563  	}
   564  	return urlsmap, token, err
   565  }
   566  
   567  func (cfg Config) InitialClusterFromName(name string) (ret string) {
   568  	if len(cfg.APUrls) == 0 {
   569  		return ""
   570  	}
   571  	n := name
   572  	if name == "" {
   573  		n = DefaultName
   574  	}
   575  	for i := range cfg.APUrls {
   576  		ret = ret + "," + n + "=" + cfg.APUrls[i].String()
   577  	}
   578  	return ret[1:]
   579  }
   580  
   581  func (cfg Config) IsNewCluster() bool { return cfg.ClusterState == ClusterStateFlagNew }
   582  func (cfg Config) ElectionTicks() int { return int(cfg.ElectionMs / cfg.TickMs) }
   583  
   584  func (cfg Config) defaultPeerHost() bool {
   585  	return len(cfg.APUrls) == 1 && cfg.APUrls[0].String() == DefaultInitialAdvertisePeerURLs
   586  }
   587  
   588  func (cfg Config) defaultClientHost() bool {
   589  	return len(cfg.ACUrls) == 1 && cfg.ACUrls[0].String() == DefaultAdvertiseClientURLs
   590  }
   591  
   592  func (cfg *Config) ClientSelfCert() (err error) {
   593  	if !cfg.ClientAutoTLS {
   594  		return nil
   595  	}
   596  	if !cfg.ClientTLSInfo.Empty() {
   597  		plog.Warningf("ignoring client auto TLS since certs given")
   598  		return nil
   599  	}
   600  	chosts := make([]string, len(cfg.LCUrls))
   601  	for i, u := range cfg.LCUrls {
   602  		chosts[i] = u.Host
   603  	}
   604  	cfg.ClientTLSInfo, err = transport.SelfCert(filepath.Join(cfg.Dir, "fixtures", "client"), chosts)
   605  	if err != nil {
   606  		return err
   607  	}
   608  	return updateCipherSuites(&cfg.ClientTLSInfo, cfg.CipherSuites)
   609  }
   610  
   611  func (cfg *Config) PeerSelfCert() (err error) {
   612  	if !cfg.PeerAutoTLS {
   613  		return nil
   614  	}
   615  	if !cfg.PeerTLSInfo.Empty() {
   616  		plog.Warningf("ignoring peer auto TLS since certs given")
   617  		return nil
   618  	}
   619  	phosts := make([]string, len(cfg.LPUrls))
   620  	for i, u := range cfg.LPUrls {
   621  		phosts[i] = u.Host
   622  	}
   623  	cfg.PeerTLSInfo, err = transport.SelfCert(filepath.Join(cfg.Dir, "fixtures", "peer"), phosts)
   624  	if err != nil {
   625  		return err
   626  	}
   627  	return updateCipherSuites(&cfg.PeerTLSInfo, cfg.CipherSuites)
   628  }
   629  
   630  // UpdateDefaultClusterFromName updates cluster advertise URLs with, if available, default host,
   631  // if advertise URLs are default values(localhost:2379,2380) AND if listen URL is 0.0.0.0.
   632  // e.g. advertise peer URL localhost:2380 or listen peer URL 0.0.0.0:2380
   633  // then the advertise peer host would be updated with machine's default host,
   634  // while keeping the listen URL's port.
   635  // User can work around this by explicitly setting URL with 127.0.0.1.
   636  // It returns the default hostname, if used, and the error, if any, from getting the machine's default host.
   637  // TODO: check whether fields are set instead of whether fields have default value
   638  func (cfg *Config) UpdateDefaultClusterFromName(defaultInitialCluster string) (string, error) {
   639  	if defaultHostname == "" || defaultHostStatus != nil {
   640  		// update 'initial-cluster' when only the name is specified (e.g. 'etcd --name=abc')
   641  		if cfg.Name != DefaultName && cfg.InitialCluster == defaultInitialCluster {
   642  			cfg.InitialCluster = cfg.InitialClusterFromName(cfg.Name)
   643  		}
   644  		return "", defaultHostStatus
   645  	}
   646  
   647  	used := false
   648  	pip, pport := cfg.LPUrls[0].Hostname(), cfg.LPUrls[0].Port()
   649  	if cfg.defaultPeerHost() && pip == "0.0.0.0" {
   650  		cfg.APUrls[0] = url.URL{Scheme: cfg.APUrls[0].Scheme, Host: fmt.Sprintf("%s:%s", defaultHostname, pport)}
   651  		used = true
   652  	}
   653  	// update 'initial-cluster' when only the name is specified (e.g. 'etcd --name=abc')
   654  	if cfg.Name != DefaultName && cfg.InitialCluster == defaultInitialCluster {
   655  		cfg.InitialCluster = cfg.InitialClusterFromName(cfg.Name)
   656  	}
   657  
   658  	cip, cport := cfg.LCUrls[0].Hostname(), cfg.LCUrls[0].Port()
   659  	if cfg.defaultClientHost() && cip == "0.0.0.0" {
   660  		cfg.ACUrls[0] = url.URL{Scheme: cfg.ACUrls[0].Scheme, Host: fmt.Sprintf("%s:%s", defaultHostname, cport)}
   661  		used = true
   662  	}
   663  	dhost := defaultHostname
   664  	if !used {
   665  		dhost = ""
   666  	}
   667  	return dhost, defaultHostStatus
   668  }
   669  
   670  // checkBindURLs returns an error if any URL uses a domain name.
   671  func checkBindURLs(urls []url.URL) error {
   672  	for _, url := range urls {
   673  		if url.Scheme == "unix" || url.Scheme == "unixs" {
   674  			continue
   675  		}
   676  		host, _, err := net.SplitHostPort(url.Host)
   677  		if err != nil {
   678  			return err
   679  		}
   680  		if host == "localhost" {
   681  			// special case for local address
   682  			// TODO: support /etc/hosts ?
   683  			continue
   684  		}
   685  		if net.ParseIP(host) == nil {
   686  			return fmt.Errorf("expected IP in URL for binding (%s)", url.String())
   687  		}
   688  	}
   689  	return nil
   690  }
   691  
   692  func checkHostURLs(urls []url.URL) error {
   693  	for _, url := range urls {
   694  		host, _, err := net.SplitHostPort(url.Host)
   695  		if err != nil {
   696  			return err
   697  		}
   698  		if host == "" {
   699  			return fmt.Errorf("unexpected empty host (%s)", url.String())
   700  		}
   701  	}
   702  	return nil
   703  }