From 8bc5ab9f8d39135518a78beee005eb8dd6dfeb57 Mon Sep 17 00:00:00 2001 From: Ajit Yagaty Date: Thu, 5 May 2016 00:47:38 -0700 Subject: [PATCH] etcd: Configuration file for etcd server. Added a new command line option to etcd server to read in a YAML based configuration file. I've also added an example configuration file with comments and a set of test cases. --- etcdmain/config.go | 275 +++++++++++++++++++------ etcdmain/config_test.go | 376 ++++++++++++++++++++++++++++------ etcdmain/etcd.conf.sample.yml | 135 ++++++++++++ etcdmain/etcd.go | 102 ++++----- etcdmain/help.go | 3 + 5 files changed, 707 insertions(+), 184 deletions(-) create mode 100644 etcdmain/etcd.conf.sample.yml diff --git a/etcdmain/config.go b/etcdmain/config.go index 975e35f0f..63028798a 100644 --- a/etcdmain/config.go +++ b/etcdmain/config.go @@ -19,6 +19,7 @@ package etcdmain import ( "flag" "fmt" + "io/ioutil" "net/url" "os" "runtime" @@ -28,7 +29,9 @@ import ( "github.com/coreos/etcd/pkg/cors" "github.com/coreos/etcd/pkg/flags" "github.com/coreos/etcd/pkg/transport" + "github.com/coreos/etcd/pkg/types" "github.com/coreos/etcd/version" + "github.com/ghodss/yaml" ) const ( @@ -44,6 +47,9 @@ const ( defaultName = "default" defaultInitialAdvertisePeerURLs = "http://localhost:2380,http://localhost:7001" + defaultAdvertiseClientURLs = "http://localhost:2379,http://localhost:4001" + defaultListenPeerURLs = "http://localhost:2380,http://localhost:7001" + defaultListenClientURLs = "http://localhost:2379,http://localhost:4001" // maxElectionMs specifies the maximum value of election timeout. // More details are listed in ../Documentation/tuning.md#time-parameters. @@ -77,49 +83,61 @@ type config struct { // member corsInfo *cors.CORSInfo - dir string - walDir string lpurls, lcurls []url.URL - maxSnapFiles uint - maxWalFiles uint - name string - snapCount uint64 + Dir string `json:"data-dir"` + WalDir string `json:"wal-dir"` + MaxSnapFiles uint `json:"max-snapshots"` + MaxWalFiles uint `json:"max-wals"` + Name string `json:"name"` + SnapCount uint64 `json:"snapshot-count"` + LPUrlsCfgFile string `json:"listen-peer-urls"` + LCUrlsCfgFile string `json:"listen-client-urls"` + CorsCfgFile string `json:"cors"` + // TickMs is the number of milliseconds between heartbeat ticks. // TODO: decouple tickMs and heartbeat tick (current heartbeat tick = 1). // make ticks a cluster wide configuration. - TickMs uint - ElectionMs uint - quotaBackendBytes int64 + TickMs uint `json:"heartbeat-interval"` + ElectionMs uint `json:"election-timeout"` + QuotaBackendBytes int64 `json:"quota-backend-bytes"` // clustering apurls, acurls []url.URL clusterState *flags.StringsFlag - dnsCluster string - dproxy string - durl string + DnsCluster string `json:"discovery-srv"` + Dproxy string `json:"discovery-proxy"` + Durl string `json:"discovery"` fallback *flags.StringsFlag - initialCluster string - initialClusterToken string - strictReconfigCheck bool + InitialCluster string `json:"initial-cluster"` + InitialClusterToken string `json:"initial-cluster-token"` + StrictReconfigCheck bool `json:"strict-reconfig-check"` + ApurlsCfgFile string `json:"initial-advertise-peer-urls"` + AcurlsCfgFile string `json:"advertise-client-urls"` + ClusterStateCfgFile string `json:"initial-cluster-state"` + FallbackCfgFile string `json:"discovery-fallback"` // proxy proxy *flags.StringsFlag - proxyFailureWaitMs uint - proxyRefreshIntervalMs uint - proxyDialTimeoutMs uint - proxyWriteTimeoutMs uint - proxyReadTimeoutMs uint + ProxyFailureWaitMs uint `json:"proxy-failure-wait"` + ProxyRefreshIntervalMs uint `json:"proxy-refresh-interval"` + ProxyDialTimeoutMs uint `json:"proxy-dial-timeout"` + ProxyWriteTimeoutMs uint `json:"proxy-write-timeout"` + ProxyReadTimeoutMs uint `json:"proxy-read-timeout"` + ProxyCfgFile string `json:"proxy"` // security clientTLSInfo, peerTLSInfo transport.TLSInfo - clientAutoTLS, peerAutoTLS bool + ClientAutoTLS bool + PeerAutoTLS bool + ClientSecurityCfgFile securityConfig `json:"client-transport-security"` + PeerSecurityCfgFile securityConfig `json:"peer-transport-security"` - // logging - debug bool - logPkgLevels string + // Debug logging + Debug bool `json:"debug"` + LogPkgLevels string `json:"log-package-levels"` - // unsafe - forceNewCluster bool + // ForceNewCluster is unsafe + ForceNewCluster bool `json:"force-new-cluster"` printVersion bool @@ -127,9 +145,20 @@ type config struct { enablePprof bool + configFile string + ignored []string } +type securityConfig struct { + CAFile string `json:"ca-file"` + CertFile string `json:"cert-file"` + KeyFile string `json:"key-file"` + CertAuth bool `json:"client-cert-auth"` + TrustedCAFile string `json:"trusted-ca-file"` + AutoTLS bool `json:"auto-tls"` +} + func NewConfig() *config { cfg := &config{ corsInfo: &cors.CORSInfo{}, @@ -155,39 +184,41 @@ func NewConfig() *config { fmt.Println(usageline) } + fs.StringVar(&cfg.configFile, "config-file", "", "Path to the server configuration file") + // member fs.Var(cfg.corsInfo, "cors", "Comma-separated white list of origins for CORS (cross-origin resource sharing).") - fs.StringVar(&cfg.dir, "data-dir", "", "Path to the data directory.") - fs.StringVar(&cfg.walDir, "wal-dir", "", "Path to the dedicated wal directory.") - fs.Var(flags.NewURLsValue("http://localhost:2380,http://localhost:7001"), "listen-peer-urls", "List of URLs to listen on for peer traffic.") - fs.Var(flags.NewURLsValue("http://localhost:2379,http://localhost:4001"), "listen-client-urls", "List of URLs to listen on for client traffic.") - fs.UintVar(&cfg.maxSnapFiles, "max-snapshots", defaultMaxSnapshots, "Maximum number of snapshot files to retain (0 is unlimited).") - fs.UintVar(&cfg.maxWalFiles, "max-wals", defaultMaxWALs, "Maximum number of wal files to retain (0 is unlimited).") - fs.StringVar(&cfg.name, "name", defaultName, "Human-readable name for this member.") - fs.Uint64Var(&cfg.snapCount, "snapshot-count", etcdserver.DefaultSnapCount, "Number of committed transactions to trigger a snapshot to disk.") + fs.StringVar(&cfg.Dir, "data-dir", "", "Path to the data directory.") + fs.StringVar(&cfg.WalDir, "wal-dir", "", "Path to the dedicated wal directory.") + fs.Var(flags.NewURLsValue(defaultListenPeerURLs), "listen-peer-urls", "List of URLs to listen on for peer traffic.") + fs.Var(flags.NewURLsValue(defaultListenClientURLs), "listen-client-urls", "List of URLs to listen on for client traffic.") + fs.UintVar(&cfg.MaxSnapFiles, "max-snapshots", defaultMaxSnapshots, "Maximum number of snapshot files to retain (0 is unlimited).") + fs.UintVar(&cfg.MaxWalFiles, "max-wals", defaultMaxWALs, "Maximum number of wal files to retain (0 is unlimited).") + fs.StringVar(&cfg.Name, "name", defaultName, "Human-readable name for this member.") + fs.Uint64Var(&cfg.SnapCount, "snapshot-count", etcdserver.DefaultSnapCount, "Number of committed transactions to trigger a snapshot to disk.") fs.UintVar(&cfg.TickMs, "heartbeat-interval", 100, "Time (in milliseconds) of a heartbeat interval.") fs.UintVar(&cfg.ElectionMs, "election-timeout", 1000, "Time (in milliseconds) for an election to timeout.") - fs.Int64Var(&cfg.quotaBackendBytes, "quota-backend-bytes", 0, "Raise alarms when backend size exceeds the given quota. 0 means use the default quota.") + fs.Int64Var(&cfg.QuotaBackendBytes, "quota-backend-bytes", 0, "Raise alarms when backend size exceeds the given quota. 0 means use the default quota.") // clustering fs.Var(flags.NewURLsValue(defaultInitialAdvertisePeerURLs), "initial-advertise-peer-urls", "List of this member's peer URLs to advertise to the rest of the cluster.") - fs.Var(flags.NewURLsValue("http://localhost:2379,http://localhost:4001"), "advertise-client-urls", "List of this member's client URLs to advertise to the public.") - fs.StringVar(&cfg.durl, "discovery", "", "Discovery URL used to bootstrap the cluster.") + fs.Var(flags.NewURLsValue(defaultAdvertiseClientURLs), "advertise-client-urls", "List of this member's client URLs to advertise to the public.") + fs.StringVar(&cfg.Durl, "discovery", "", "Discovery URL used to bootstrap the cluster.") fs.Var(cfg.fallback, "discovery-fallback", fmt.Sprintf("Valid values include %s", strings.Join(cfg.fallback.Values, ", "))) if err := cfg.fallback.Set(fallbackFlagProxy); err != nil { // Should never happen. plog.Panicf("unexpected error setting up discovery-fallback flag: %v", err) } - fs.StringVar(&cfg.dproxy, "discovery-proxy", "", "HTTP proxy to use for traffic to discovery service.") - fs.StringVar(&cfg.dnsCluster, "discovery-srv", "", "DNS domain used to bootstrap initial cluster.") - fs.StringVar(&cfg.initialCluster, "initial-cluster", initialClusterFromName(defaultName), "Initial cluster configuration for bootstrapping.") - fs.StringVar(&cfg.initialClusterToken, "initial-cluster-token", "etcd-cluster", "Initial cluster token for the etcd cluster during bootstrap.") + fs.StringVar(&cfg.Dproxy, "discovery-proxy", "", "HTTP proxy to use for traffic to discovery service.") + fs.StringVar(&cfg.DnsCluster, "discovery-srv", "", "DNS domain used to bootstrap initial cluster.") + fs.StringVar(&cfg.InitialCluster, "initial-cluster", initialClusterFromName(defaultName), "Initial cluster configuration for bootstrapping.") + fs.StringVar(&cfg.InitialClusterToken, "initial-cluster-token", "etcd-cluster", "Initial cluster token for the etcd cluster during bootstrap.") fs.Var(cfg.clusterState, "initial-cluster-state", "Initial cluster state ('new' or 'existing').") if err := cfg.clusterState.Set(clusterStateFlagNew); err != nil { // Should never happen. plog.Panicf("unexpected error setting up clusterStateFlag: %v", err) } - fs.BoolVar(&cfg.strictReconfigCheck, "strict-reconfig-check", false, "Reject reconfiguration requests that would cause quorum loss.") + fs.BoolVar(&cfg.StrictReconfigCheck, "strict-reconfig-check", false, "Reject reconfiguration requests that would cause quorum loss.") // proxy fs.Var(cfg.proxy, "proxy", fmt.Sprintf("Valid values include %s", strings.Join(cfg.proxy.Values, ", "))) @@ -195,11 +226,11 @@ func NewConfig() *config { // Should never happen. plog.Panicf("unexpected error setting up proxyFlag: %v", err) } - fs.UintVar(&cfg.proxyFailureWaitMs, "proxy-failure-wait", 5000, "Time (in milliseconds) an endpoint will be held in a failed state.") - fs.UintVar(&cfg.proxyRefreshIntervalMs, "proxy-refresh-interval", 30000, "Time (in milliseconds) of the endpoints refresh interval.") - fs.UintVar(&cfg.proxyDialTimeoutMs, "proxy-dial-timeout", 1000, "Time (in milliseconds) for a dial to timeout.") - fs.UintVar(&cfg.proxyWriteTimeoutMs, "proxy-write-timeout", 5000, "Time (in milliseconds) for a write to timeout.") - fs.UintVar(&cfg.proxyReadTimeoutMs, "proxy-read-timeout", 0, "Time (in milliseconds) for a read to timeout.") + fs.UintVar(&cfg.ProxyFailureWaitMs, "proxy-failure-wait", 5000, "Time (in milliseconds) an endpoint will be held in a failed state.") + fs.UintVar(&cfg.ProxyRefreshIntervalMs, "proxy-refresh-interval", 30000, "Time (in milliseconds) of the endpoints refresh interval.") + fs.UintVar(&cfg.ProxyDialTimeoutMs, "proxy-dial-timeout", 1000, "Time (in milliseconds) for a dial to timeout.") + fs.UintVar(&cfg.ProxyWriteTimeoutMs, "proxy-write-timeout", 5000, "Time (in milliseconds) for a write to timeout.") + fs.UintVar(&cfg.ProxyReadTimeoutMs, "proxy-read-timeout", 0, "Time (in milliseconds) for a read to timeout.") // security fs.StringVar(&cfg.clientTLSInfo.CAFile, "ca-file", "", "DEPRECATED: Path to the client server TLS CA file.") @@ -207,20 +238,20 @@ func NewConfig() *config { fs.StringVar(&cfg.clientTLSInfo.KeyFile, "key-file", "", "Path to the client server TLS key file.") fs.BoolVar(&cfg.clientTLSInfo.ClientCertAuth, "client-cert-auth", false, "Enable client cert authentication.") fs.StringVar(&cfg.clientTLSInfo.TrustedCAFile, "trusted-ca-file", "", "Path to the client server TLS trusted CA key file.") - fs.BoolVar(&cfg.clientAutoTLS, "auto-tls", false, "Client TLS using generated certificates") + fs.BoolVar(&cfg.ClientAutoTLS, "auto-tls", false, "Client TLS using generated certificates") fs.StringVar(&cfg.peerTLSInfo.CAFile, "peer-ca-file", "", "DEPRECATED: Path to the peer server TLS CA file.") fs.StringVar(&cfg.peerTLSInfo.CertFile, "peer-cert-file", "", "Path to the peer server TLS cert file.") fs.StringVar(&cfg.peerTLSInfo.KeyFile, "peer-key-file", "", "Path to the peer server TLS key file.") fs.BoolVar(&cfg.peerTLSInfo.ClientCertAuth, "peer-client-cert-auth", false, "Enable peer client cert authentication.") fs.StringVar(&cfg.peerTLSInfo.TrustedCAFile, "peer-trusted-ca-file", "", "Path to the peer server TLS trusted CA file.") - fs.BoolVar(&cfg.peerAutoTLS, "peer-auto-tls", false, "Peer TLS using generated certificates") + fs.BoolVar(&cfg.PeerAutoTLS, "peer-auto-tls", false, "Peer TLS using generated certificates") // logging - fs.BoolVar(&cfg.debug, "debug", false, "Enable debug-level logging for etcd.") - fs.StringVar(&cfg.logPkgLevels, "log-package-levels", "", "Specify a particular log level for each etcd package (eg: 'etcdmain=CRITICAL,etcdserver=DEBUG').") + fs.BoolVar(&cfg.Debug, "debug", false, "Enable debug-level logging for etcd.") + fs.StringVar(&cfg.LogPkgLevels, "log-package-levels", "", "Specify a particular log level for each etcd package (eg: 'etcdmain=CRITICAL,etcdserver=DEBUG').") // unsafe - fs.BoolVar(&cfg.forceNewCluster, "force-new-cluster", false, "Force to create a new one member cluster.") + fs.BoolVar(&cfg.ForceNewCluster, "force-new-cluster", false, "Force to create a new one member cluster.") // version fs.BoolVar(&cfg.printVersion, "version", false, "Print the version and exit.") @@ -268,25 +299,23 @@ func (cfg *config) Parse(arguments []string) error { os.Exit(0) } + var err error + if cfg.configFile != "" { + plog.Infof("Loading server configuration from %q", cfg.configFile) + err = cfg.configFromFile() + } else { + err = cfg.configFromCmdLine() + } + + return err +} + +func (cfg *config) configFromCmdLine() error { err := flags.SetFlagsFromEnv("ETCD", cfg.FlagSet) if err != nil { plog.Fatalf("%v", err) } - set := make(map[string]bool) - cfg.FlagSet.Visit(func(f *flag.Flag) { - set[f.Name] = true - }) - nSet := 0 - for _, v := range []bool{set["discovery"], set["initial-cluster"], set["discovery-srv"]} { - if v { - nSet += 1 - } - } - if nSet > 1 { - return ErrConflictBootstrapFlags - } - flags.SetBindAddrFromAddr(cfg.FlagSet, "peer-bind-addr", "peer-addr") flags.SetBindAddrFromAddr(cfg.FlagSet, "bind-addr", "addr") @@ -307,16 +336,126 @@ func (cfg *config) Parse(arguments []string) error { return err } + return cfg.validateConfig(func(field string) bool { + return flags.IsSet(cfg.FlagSet, field) + }) +} + +func (cfg *config) configFromFile() error { + b, err := ioutil.ReadFile(cfg.configFile) + if err != nil { + return err + } + + err = yaml.Unmarshal(b, cfg) + if err != nil { + return err + } + + if cfg.LPUrlsCfgFile != "" { + u, err := types.NewURLs(strings.Split(cfg.LPUrlsCfgFile, ",")) + if err != nil { + plog.Fatalf("unexpected error setting up listen-peer-urls: %v", err) + } + cfg.lpurls = []url.URL(u) + } + + if cfg.LCUrlsCfgFile != "" { + u, err := types.NewURLs(strings.Split(cfg.LCUrlsCfgFile, ",")) + if err != nil { + plog.Fatalf("unexpected error setting up listen-client-urls: %v", err) + } + cfg.lcurls = []url.URL(u) + } + + if cfg.CorsCfgFile != "" { + if err := cfg.corsInfo.Set(cfg.CorsCfgFile); err != nil { + plog.Panicf("unexpected error setting up cors: %v", err) + } + } + + if cfg.ApurlsCfgFile != "" { + u, err := types.NewURLs(strings.Split(cfg.ApurlsCfgFile, ",")) + if err != nil { + plog.Fatalf("unexpected error setting up initial-advertise-peer-urls: %v", err) + } + cfg.apurls = []url.URL(u) + } + + if cfg.AcurlsCfgFile != "" { + u, err := types.NewURLs(strings.Split(cfg.AcurlsCfgFile, ",")) + if err != nil { + plog.Fatalf("unexpected error setting up advertise-peer-urls: %v", err) + } + cfg.acurls = []url.URL(u) + } + + if cfg.ClusterStateCfgFile != "" { + if err := cfg.clusterState.Set(cfg.ClusterStateCfgFile); err != nil { + plog.Panicf("unexpected error setting up clusterStateFlag: %v", err) + } + } + + if cfg.FallbackCfgFile != "" { + if err := cfg.fallback.Set(cfg.FallbackCfgFile); err != nil { + plog.Panicf("unexpected error setting up discovery-fallback flag: %v", err) + } + } + + if cfg.ProxyCfgFile != "" { + if err := cfg.proxy.Set(cfg.ProxyCfgFile); err != nil { + plog.Panicf("unexpected error setting up proxyFlag: %v", err) + } + } + + copySecurityDetails := func(tls *transport.TLSInfo, ysc *securityConfig) { + tls.CAFile = ysc.CAFile + tls.CertFile = ysc.CertFile + tls.KeyFile = ysc.KeyFile + tls.ClientCertAuth = ysc.CertAuth + tls.TrustedCAFile = ysc.TrustedCAFile + } + copySecurityDetails(&cfg.clientTLSInfo, &cfg.ClientSecurityCfgFile) + copySecurityDetails(&cfg.peerTLSInfo, &cfg.PeerSecurityCfgFile) + cfg.ClientAutoTLS = cfg.ClientSecurityCfgFile.AutoTLS + cfg.PeerAutoTLS = cfg.PeerSecurityCfgFile.AutoTLS + + fieldsToBeChecked := map[string]bool{ + "discovery": (cfg.Durl != ""), + "listen-client-urls": (cfg.LCUrlsCfgFile != ""), + "advertise-client-urls": (cfg.AcurlsCfgFile != ""), + "initial-cluster": (cfg.InitialCluster != ""), + "discovery-srv": (cfg.DnsCluster != ""), + } + + return cfg.validateConfig(func(field string) bool { + return fieldsToBeChecked[field] + }) +} + +func (cfg *config) validateConfig(isSet func(field string) bool) error { // when etcd runs in member mode user needs to set --advertise-client-urls if --listen-client-urls is set. // TODO(yichengq): check this for joining through discovery service case - mayFallbackToProxy := flags.IsSet(cfg.FlagSet, "discovery") && cfg.fallback.String() == fallbackFlagProxy + mayFallbackToProxy := isSet("discovery") && cfg.fallback.String() == fallbackFlagProxy mayBeProxy := cfg.proxy.String() != proxyFlagOff || mayFallbackToProxy if !mayBeProxy { - if flags.IsSet(cfg.FlagSet, "listen-client-urls") && !flags.IsSet(cfg.FlagSet, "advertise-client-urls") { + if isSet("listen-client-urls") && !isSet("advertise-client-urls") { return errUnsetAdvertiseClientURLsFlag } } + // Check if conflicting flags are passed. + nSet := 0 + for _, v := range []bool{isSet("discovery"), isSet("initial-cluster"), isSet("discovery-srv")} { + if v { + nSet += 1 + } + } + + if nSet > 1 { + return ErrConflictBootstrapFlags + } + if 5*cfg.TickMs > cfg.ElectionMs { return fmt.Errorf("--election-timeout[%vms] should be at least as 5 times as --heartbeat-interval[%vms]", cfg.ElectionMs, cfg.TickMs) } diff --git a/etcdmain/config_test.go b/etcdmain/config_test.go index 9f7b7444b..55ad57cf6 100644 --- a/etcdmain/config_test.go +++ b/etcdmain/config_test.go @@ -15,9 +15,15 @@ package etcdmain import ( + "fmt" + "io/ioutil" "net/url" + "os" "reflect" + "strings" "testing" + + "github.com/ghodss/yaml" ) func TestConfigParsingMemberFlags(t *testing.T) { @@ -32,42 +38,56 @@ func TestConfigParsingMemberFlags(t *testing.T) { // it should be set if -listen-client-urls is set "-advertise-client-urls=http://localhost:7000,https://localhost:7001", } - wcfg := &config{ - dir: "testdir", - lpurls: []url.URL{{Scheme: "http", Host: "localhost:8000"}, {Scheme: "https", Host: "localhost:8001"}}, - lcurls: []url.URL{{Scheme: "http", Host: "localhost:7000"}, {Scheme: "https", Host: "localhost:7001"}}, - maxSnapFiles: 10, - maxWalFiles: 10, - name: "testname", - snapCount: 10, - } cfg := NewConfig() err := cfg.Parse(args) if err != nil { t.Fatal(err) } - if cfg.dir != wcfg.dir { - t.Errorf("dir = %v, want %v", cfg.dir, wcfg.dir) + + validateMemberFlags(t, cfg) +} + +func TestConfigFileMemberFields(t *testing.T) { + yc := struct { + Dir string `json:"data-dir"` + MaxSnapFiles uint `json:"max-snapshots"` + MaxWalFiles uint `json:"max-wals"` + Name string `json:"name"` + SnapCount uint64 `json:"snapshot-count"` + LPUrls string `json:"listen-peer-urls"` + LCUrls string `json:"listen-client-urls"` + AcurlsCfgFile string `json:"advertise-client-urls"` + }{ + "testdir", + 10, + 10, + "testname", + 10, + "http://localhost:8000,https://localhost:8001", + "http://localhost:7000,https://localhost:7001", + "http://localhost:7000,https://localhost:7001", } - if cfg.maxSnapFiles != wcfg.maxSnapFiles { - t.Errorf("maxsnap = %v, want %v", cfg.maxSnapFiles, wcfg.maxSnapFiles) + + b, err := yaml.Marshal(&yc) + if err != nil { + t.Fatal(err) } - if cfg.maxWalFiles != wcfg.maxWalFiles { - t.Errorf("maxwal = %v, want %v", cfg.maxWalFiles, wcfg.maxWalFiles) + + tmpfile := mustCreateCfgFile(t, b) + defer os.Remove(tmpfile.Name()) + + args := []string{ + fmt.Sprintf("--config-file=%s", tmpfile.Name()), } - if cfg.name != wcfg.name { - t.Errorf("name = %v, want %v", cfg.name, wcfg.name) - } - if cfg.snapCount != wcfg.snapCount { - t.Errorf("snapcount = %v, want %v", cfg.snapCount, wcfg.snapCount) - } - if !reflect.DeepEqual(cfg.lpurls, wcfg.lpurls) { - t.Errorf("listen-peer-urls = %v, want %v", cfg.lpurls, wcfg.lpurls) - } - if !reflect.DeepEqual(cfg.lcurls, wcfg.lcurls) { - t.Errorf("listen-client-urls = %v, want %v", cfg.lcurls, wcfg.lcurls) + + cfg := NewConfig() + err = cfg.Parse(args) + if err != nil { + t.Fatal(err) } + + validateMemberFlags(t, cfg) } func TestConfigParsingClusteringFlags(t *testing.T) { @@ -79,37 +99,51 @@ func TestConfigParsingClusteringFlags(t *testing.T) { "-advertise-client-urls=http://localhost:7000,https://localhost:7001", "-discovery-fallback=exit", } - wcfg := NewConfig() - wcfg.apurls = []url.URL{{Scheme: "http", Host: "localhost:8000"}, {Scheme: "https", Host: "localhost:8001"}} - wcfg.acurls = []url.URL{{Scheme: "http", Host: "localhost:7000"}, {Scheme: "https", Host: "localhost:7001"}} - wcfg.clusterState.Set(clusterStateFlagExisting) - wcfg.fallback.Set(fallbackFlagExit) - wcfg.initialCluster = "0=http://localhost:8000" - wcfg.initialClusterToken = "etcdtest" cfg := NewConfig() err := cfg.Parse(args) if err != nil { t.Fatal(err) } - if cfg.clusterState.String() != wcfg.clusterState.String() { - t.Errorf("clusterState = %v, want %v", cfg.clusterState, wcfg.clusterState) + + validateClusteringFlags(t, cfg) +} + +func TestConfigFileClusteringFields(t *testing.T) { + yc := struct { + InitialCluster string `json:"initial-cluster"` + ClusterState string `json:"initial-cluster-state"` + InitialClusterToken string `json:"initial-cluster-token"` + Apurls string `json:"initial-advertise-peer-urls"` + Acurls string `json:"advertise-client-urls"` + Fallback string `json:"discovery-fallback"` + }{ + "0=http://localhost:8000", + "existing", + "etcdtest", + "http://localhost:8000,https://localhost:8001", + "http://localhost:7000,https://localhost:7001", + "exit", } - if cfg.fallback.String() != wcfg.fallback.String() { - t.Errorf("fallback = %v, want %v", cfg.fallback, wcfg.fallback) + + b, err := yaml.Marshal(&yc) + if err != nil { + t.Fatal(err) } - if cfg.initialCluster != wcfg.initialCluster { - t.Errorf("initialCluster = %v, want %v", cfg.initialCluster, wcfg.initialCluster) + + tmpfile := mustCreateCfgFile(t, b) + defer os.Remove(tmpfile.Name()) + + args := []string{ + fmt.Sprintf("--config-file=%s", tmpfile.Name()), } - if cfg.initialClusterToken != wcfg.initialClusterToken { - t.Errorf("initialClusterToken = %v, want %v", cfg.initialClusterToken, wcfg.initialClusterToken) - } - if !reflect.DeepEqual(cfg.apurls, wcfg.apurls) { - t.Errorf("initial-advertise-peer-urls = %v, want %v", cfg.lpurls, wcfg.lpurls) - } - if !reflect.DeepEqual(cfg.acurls, wcfg.acurls) { - t.Errorf("advertise-client-urls = %v, want %v", cfg.lcurls, wcfg.lcurls) + cfg := NewConfig() + err = cfg.Parse(args) + if err != nil { + t.Fatal(err) } + + validateClusteringFlags(t, cfg) } func TestConfigParsingOtherFlags(t *testing.T) { @@ -124,33 +158,55 @@ func TestConfigParsingOtherFlags(t *testing.T) { "-force-new-cluster=true", } - wcfg := NewConfig() - wcfg.proxy.Set(proxyFlagReadonly) - wcfg.clientTLSInfo.CAFile = "cafile" - wcfg.clientTLSInfo.CertFile = "certfile" - wcfg.clientTLSInfo.KeyFile = "keyfile" - wcfg.peerTLSInfo.CAFile = "peercafile" - wcfg.peerTLSInfo.CertFile = "peercertfile" - wcfg.peerTLSInfo.KeyFile = "peerkeyfile" - wcfg.forceNewCluster = true - cfg := NewConfig() err := cfg.Parse(args) if err != nil { t.Fatal(err) } - if cfg.proxy.String() != wcfg.proxy.String() { - t.Errorf("proxy = %v, want %v", cfg.proxy, wcfg.proxy) + + validateOtherFlags(t, cfg) +} + +func TestConfigFileOtherFields(t *testing.T) { + yc := struct { + ProxyCfgFile string `json:"proxy"` + ClientSecurityCfgFile securityConfig `json:"client-transport-security"` + PeerSecurityCfgFile securityConfig `json:"peer-transport-security"` + ForceNewCluster bool `json:"force-new-cluster"` + }{ + "readonly", + securityConfig{ + CAFile: "cafile", + CertFile: "certfile", + KeyFile: "keyfile", + }, + securityConfig{ + CAFile: "peercafile", + CertFile: "peercertfile", + KeyFile: "peerkeyfile", + }, + true, } - if cfg.clientTLSInfo.String() != wcfg.clientTLSInfo.String() { - t.Errorf("clientTLS = %v, want %v", cfg.clientTLSInfo, wcfg.clientTLSInfo) + + b, err := yaml.Marshal(&yc) + if err != nil { + t.Fatal(err) } - if cfg.peerTLSInfo.String() != wcfg.peerTLSInfo.String() { - t.Errorf("peerTLS = %v, want %v", cfg.peerTLSInfo, wcfg.peerTLSInfo) + + tmpfile := mustCreateCfgFile(t, b) + defer os.Remove(tmpfile.Name()) + + args := []string{ + fmt.Sprintf("--config-file=%s", tmpfile.Name()), } - if cfg.forceNewCluster != wcfg.forceNewCluster { - t.Errorf("forceNewCluster = %t, want %t", cfg.forceNewCluster, wcfg.forceNewCluster) + + cfg := NewConfig() + err = cfg.Parse(args) + if err != nil { + t.Fatal(err) } + + validateOtherFlags(t, cfg) } func TestConfigParsingV1Flags(t *testing.T) { @@ -212,6 +268,52 @@ func TestConfigParsingConflictClusteringFlags(t *testing.T) { } } +func TestConfigFileConflictClusteringFlags(t *testing.T) { + tests := []struct { + InitialCluster string `json:"initial-cluster"` + DnsCluster string `json:"discovery-srv"` + Durl string `json:"discovery"` + }{ + { + InitialCluster: "0=localhost:8000", + Durl: "http://example.com/abc", + }, + { + DnsCluster: "example.com", + Durl: "http://example.com/abc", + }, + { + InitialCluster: "0=localhost:8000", + DnsCluster: "example.com", + }, + { + InitialCluster: "0=localhost:8000", + Durl: "http://example.com/abc", + DnsCluster: "example.com", + }, + } + + for i, tt := range tests { + b, err := yaml.Marshal(&tt) + if err != nil { + t.Fatal(err) + } + + tmpfile := mustCreateCfgFile(t, b) + defer os.Remove(tmpfile.Name()) + + args := []string{ + fmt.Sprintf("--config-file=%s", tmpfile.Name()), + } + + cfg := NewConfig() + err = cfg.Parse(args) + if err != ErrConflictBootstrapFlags { + t.Errorf("%d: err = %v, want %v", i, err, ErrConflictBootstrapFlags) + } + } +} + func TestConfigParsingMissedAdvertiseClientURLsFlag(t *testing.T) { tests := []struct { args []string @@ -354,3 +456,147 @@ func TestConfigShouldFallbackToProxy(t *testing.T) { } } } + +func TestConfigFileElectionTimeout(t *testing.T) { + tests := []struct { + TickMs uint `json:"heartbeat-interval"` + ElectionMs uint `json:"election-timeout"` + errStr string + }{ + { + ElectionMs: 1000, + TickMs: 800, + errStr: "should be at least as 5 times as", + }, + { + ElectionMs: 60000, + errStr: "is too long, and should be set less than", + }, + } + + for i, tt := range tests { + b, err := yaml.Marshal(&tt) + if err != nil { + t.Fatal(err) + } + + tmpfile := mustCreateCfgFile(t, b) + defer os.Remove(tmpfile.Name()) + + args := []string{ + fmt.Sprintf("--config-file=%s", tmpfile.Name()), + } + + cfg := NewConfig() + err = cfg.Parse(args) + if !strings.Contains(err.Error(), tt.errStr) { + t.Errorf("%d: Wrong err = %v", i, err) + } + } +} + +func mustCreateCfgFile(t *testing.T, b []byte) *os.File { + tmpfile, err := ioutil.TempFile("", "servercfg") + if err != nil { + t.Fatal(err) + } + + _, err = tmpfile.Write(b) + if err != nil { + t.Fatal(err) + } + err = tmpfile.Close() + if err != nil { + t.Fatal(err) + } + + return tmpfile +} + +func validateMemberFlags(t *testing.T, cfg *config) { + wcfg := &config{ + Dir: "testdir", + lpurls: []url.URL{{Scheme: "http", Host: "localhost:8000"}, {Scheme: "https", Host: "localhost:8001"}}, + lcurls: []url.URL{{Scheme: "http", Host: "localhost:7000"}, {Scheme: "https", Host: "localhost:7001"}}, + MaxSnapFiles: 10, + MaxWalFiles: 10, + Name: "testname", + SnapCount: 10, + } + + if cfg.Dir != wcfg.Dir { + t.Errorf("dir = %v, want %v", cfg.Dir, wcfg.Dir) + } + if cfg.MaxSnapFiles != wcfg.MaxSnapFiles { + t.Errorf("maxsnap = %v, want %v", cfg.MaxSnapFiles, wcfg.MaxSnapFiles) + } + if cfg.MaxWalFiles != wcfg.MaxWalFiles { + t.Errorf("maxwal = %v, want %v", cfg.MaxWalFiles, wcfg.MaxWalFiles) + } + if cfg.Name != wcfg.Name { + t.Errorf("name = %v, want %v", cfg.Name, wcfg.Name) + } + if cfg.SnapCount != wcfg.SnapCount { + t.Errorf("snapcount = %v, want %v", cfg.SnapCount, wcfg.SnapCount) + } + if !reflect.DeepEqual(cfg.lpurls, wcfg.lpurls) { + t.Errorf("listen-peer-urls = %v, want %v", cfg.lpurls, wcfg.lpurls) + } + if !reflect.DeepEqual(cfg.lcurls, wcfg.lcurls) { + t.Errorf("listen-client-urls = %v, want %v", cfg.lcurls, wcfg.lcurls) + } +} + +func validateClusteringFlags(t *testing.T, cfg *config) { + wcfg := NewConfig() + wcfg.apurls = []url.URL{{Scheme: "http", Host: "localhost:8000"}, {Scheme: "https", Host: "localhost:8001"}} + wcfg.acurls = []url.URL{{Scheme: "http", Host: "localhost:7000"}, {Scheme: "https", Host: "localhost:7001"}} + wcfg.clusterState.Set(clusterStateFlagExisting) + wcfg.fallback.Set(fallbackFlagExit) + wcfg.InitialCluster = "0=http://localhost:8000" + wcfg.InitialClusterToken = "etcdtest" + + if cfg.clusterState.String() != wcfg.clusterState.String() { + t.Errorf("clusterState = %v, want %v", cfg.clusterState, wcfg.clusterState) + } + if cfg.fallback.String() != wcfg.fallback.String() { + t.Errorf("fallback = %v, want %v", cfg.fallback, wcfg.fallback) + } + if cfg.InitialCluster != wcfg.InitialCluster { + t.Errorf("initialCluster = %v, want %v", cfg.InitialCluster, wcfg.InitialCluster) + } + if cfg.InitialClusterToken != wcfg.InitialClusterToken { + t.Errorf("initialClusterToken = %v, want %v", cfg.InitialClusterToken, wcfg.InitialClusterToken) + } + if !reflect.DeepEqual(cfg.apurls, wcfg.apurls) { + t.Errorf("initial-advertise-peer-urls = %v, want %v", cfg.lpurls, wcfg.lpurls) + } + if !reflect.DeepEqual(cfg.acurls, wcfg.acurls) { + t.Errorf("advertise-client-urls = %v, want %v", cfg.lcurls, wcfg.lcurls) + } +} + +func validateOtherFlags(t *testing.T, cfg *config) { + wcfg := NewConfig() + wcfg.proxy.Set(proxyFlagReadonly) + wcfg.clientTLSInfo.CAFile = "cafile" + wcfg.clientTLSInfo.CertFile = "certfile" + wcfg.clientTLSInfo.KeyFile = "keyfile" + wcfg.peerTLSInfo.CAFile = "peercafile" + wcfg.peerTLSInfo.CertFile = "peercertfile" + wcfg.peerTLSInfo.KeyFile = "peerkeyfile" + wcfg.ForceNewCluster = true + + if cfg.proxy.String() != wcfg.proxy.String() { + t.Errorf("proxy = %v, want %v", cfg.proxy, wcfg.proxy) + } + if cfg.clientTLSInfo.String() != wcfg.clientTLSInfo.String() { + t.Errorf("clientTLS = %v, want %v", cfg.clientTLSInfo, wcfg.clientTLSInfo) + } + if cfg.peerTLSInfo.String() != wcfg.peerTLSInfo.String() { + t.Errorf("peerTLS = %v, want %v", cfg.peerTLSInfo, wcfg.peerTLSInfo) + } + if cfg.ForceNewCluster != wcfg.ForceNewCluster { + t.Errorf("forceNewCluster = %t, want %t", cfg.ForceNewCluster, wcfg.ForceNewCluster) + } +} diff --git a/etcdmain/etcd.conf.sample.yml b/etcdmain/etcd.conf.sample.yml new file mode 100644 index 000000000..4f520d010 --- /dev/null +++ b/etcdmain/etcd.conf.sample.yml @@ -0,0 +1,135 @@ +# This is the configuration file for the etcd server. + +# Human-readable name for this member. +name: 'default' + +# Path to the data directory. +data-dir: + +# Path to the dedicated wal directory. +wal-dir: + +# Number of committed transactions to trigger a snapshot to disk. +snapshot-count: 10000 + +# Time (in milliseconds) of a heartbeat interval. +heartbeat-interval: 100 + +# Time (in milliseconds) for an election to timeout. +election-timeout: 1000 + +# Raise alarms when backend size exceeds the given quota. 0 means use the +# default quota. +quota-backend-bytes: 0 + +# List of comma separated URLs to listen on for peer traffic. +listen-peer-urls: http://localhost:2380,http://localhost:7001 + +# List of comma separated URLs to listen on for client traffic. +listen-client-urls: http://localhost:2379,http://localhost:4001 + +# Maximum number of snapshot files to retain (0 is unlimited). +max-snapshots: 5 + +# Maximum number of wal files to retain (0 is unlimited). +max-wals: 5 + +# Comma-separated white list of origins for CORS (cross-origin resource sharing). +cors: + +# List of this member's peer URLs to advertise to the rest of the cluster. +# The URLs needed to be a comma-separated list. +initial-advertise-peer-urls: http://localhost:2380,http://localhost:7001 + +# List of this member's client URLs to advertise to the public. +# The URLs needed to be a comma-separated list. +advertise-client-urls: http://localhost:2379,http://localhost:4001 + +# Discovery URL used to bootstrap the cluster. +discovery: + +# Valid values include 'exit', 'proxy' +discovery-fallback: 'proxy' + +# HTTP proxy to use for traffic to discovery service. +discovery-proxy: + +# DNS domain used to bootstrap initial cluster. +discovery-srv: + +# Initial cluster configuration for bootstrapping. +initial-cluster: + +# Initial cluster token for the etcd cluster during bootstrap. +initial-cluster-token: 'etcd-cluster' + +# Initial cluster state ('new' or 'existing'). +initial-cluster-state: 'new' + +# Reject reconfiguration requests that would cause quorum loss. +strict-reconfig-check: false + +# Valid values include 'on', 'readonly', 'off' +proxy: 'off' + +# Time (in milliseconds) an endpoint will be held in a failed state. +proxy-failure-wait: 5000 + +# Time (in milliseconds) of the endpoints refresh interval. +proxy-refresh-interval: 30000 + +# Time (in milliseconds) for a dial to timeout. +proxy-dial-timeout: 1000 + +# Time (in milliseconds) for a write to timeout. +proxy-write-timeout: 5000 + +# Time (in milliseconds) for a read to timeout. +proxy-read-timeout: 0 + +client-transport-security: + # DEPRECATED: Path to the client server TLS CA file. + ca-file: + + # Path to the client server TLS cert file. + cert-file: + + # Path to the client server TLS key file. + key-file: + + # Enable client cert authentication. + client-cert-auth: false + + # Path to the client server TLS trusted CA key file. + trusted-ca-file: + + # Client TLS using generated certificates + auto-tls: false + +peer-transport-security: + # DEPRECATED: Path to the peer server TLS CA file. + ca-file: + + # Path to the peer server TLS cert file. + peer-cert-file: + + # Path to the peer server TLS key file. + peer-key-file: + + # Enable peer client cert authentication. + peer-client-cert-auth: false + + # Path to the peer server TLS trusted CA key file. + peer-trusted-ca-file: + + # Peer TLS using generated certificates. + auto-tls: false + +# Enable debug-level logging for etcd. +debug: false + +# Specify a particular log level for each etcd package (eg: 'etcdmain=CRITICAL,etcdserver=DEBUG'. +log-package-levels: + +# Force to create a new one member cluster. +force-new-cluster: false diff --git a/etcdmain/etcd.go b/etcdmain/etcd.go index 5f0eb1211..7091e9257 100644 --- a/etcdmain/etcd.go +++ b/etcdmain/etcd.go @@ -101,16 +101,16 @@ func Main() { plog.Infof("setting maximum number of CPUs to %d, total number of available CPUs is %d", GoMaxProcs, runtime.NumCPU()) // TODO: check whether fields are set instead of whether fields have default value - if cfg.name != defaultName && cfg.initialCluster == initialClusterFromName(defaultName) { - cfg.initialCluster = initialClusterFromName(cfg.name) + if cfg.Name != defaultName && cfg.InitialCluster == initialClusterFromName(defaultName) { + cfg.InitialCluster = initialClusterFromName(cfg.Name) } - if cfg.dir == "" { - cfg.dir = fmt.Sprintf("%v.etcd", cfg.name) - plog.Warningf("no data-dir provided, using default data-dir ./%s", cfg.dir) + if cfg.Dir == "" { + cfg.Dir = fmt.Sprintf("%v.etcd", cfg.Name) + plog.Warningf("no data-dir provided, using default data-dir ./%s", cfg.Dir) } - which := identifyDataDirOrDie(cfg.dir) + which := identifyDataDirOrDie(cfg.Dir) if which != dirEmpty { plog.Noticef("the server is already initialized as %v before, starting as etcd %v...", which, which) switch which { @@ -141,17 +141,17 @@ func Main() { if derr, ok := err.(*etcdserver.DiscoveryError); ok { switch derr.Err { case discovery.ErrDuplicateID: - plog.Errorf("member %q has previously registered with discovery service token (%s).", cfg.name, cfg.durl) - plog.Errorf("But etcd could not find valid cluster configuration in the given data dir (%s).", cfg.dir) + plog.Errorf("member %q has previously registered with discovery service token (%s).", cfg.Name, cfg.Durl) + plog.Errorf("But etcd could not find valid cluster configuration in the given data dir (%s).", cfg.Dir) plog.Infof("Please check the given data dir path if the previous bootstrap succeeded") plog.Infof("or use a new discovery token if the previous bootstrap failed.") case discovery.ErrDuplicateName: - plog.Errorf("member with duplicated name has registered with discovery service token(%s).", cfg.durl) + plog.Errorf("member with duplicated name has registered with discovery service token(%s).", cfg.Durl) plog.Errorf("please check (cURL) the discovery token for more information.") plog.Errorf("please do not reuse the discovery token and generate a new one to bootstrap the cluster.") default: plog.Errorf("%v", err) - plog.Infof("discovery token %s was used, but failed to bootstrap the cluster.", cfg.durl) + plog.Infof("discovery token %s was used, but failed to bootstrap the cluster.", cfg.Durl) plog.Infof("please generate a new discovery token and try to bootstrap again.") } os.Exit(1) @@ -159,13 +159,13 @@ func Main() { if strings.Contains(err.Error(), "include") && strings.Contains(err.Error(), "--initial-cluster") { plog.Infof("%v", err) - if cfg.initialCluster == initialClusterFromName(cfg.name) { + if cfg.InitialCluster == initialClusterFromName(cfg.Name) { plog.Infof("forgot to set --initial-cluster flag?") } if types.URLs(cfg.apurls).String() == defaultInitialAdvertisePeerURLs { plog.Infof("forgot to set --initial-advertise-peer-urls flag?") } - if cfg.initialCluster == initialClusterFromName(cfg.name) && len(cfg.durl) == 0 { + if cfg.InitialCluster == initialClusterFromName(cfg.Name) && len(cfg.Durl) == 0 { plog.Infof("if you want to use discovery service, please set --discovery flag.") } os.Exit(1) @@ -202,16 +202,16 @@ func startEtcd(cfg *config) (<-chan struct{}, error) { return nil, fmt.Errorf("error setting up initial cluster: %v", err) } - if cfg.peerAutoTLS && cfg.peerTLSInfo.Empty() { + if cfg.PeerAutoTLS && cfg.peerTLSInfo.Empty() { var phosts []string for _, u := range cfg.lpurls { phosts = append(phosts, u.Host) } - cfg.peerTLSInfo, err = transport.SelfCert(path.Join(cfg.dir, "fixtures/peer"), phosts) + cfg.peerTLSInfo, err = transport.SelfCert(path.Join(cfg.Dir, "fixtures/peer"), phosts) if err != nil { plog.Fatalf("could not get certs (%v)", err) } - } else if cfg.peerAutoTLS { + } else if cfg.PeerAutoTLS { plog.Warningf("ignoring peer auto TLS since certs given") } @@ -257,16 +257,16 @@ func startEtcd(cfg *config) (<-chan struct{}, error) { plns = append(plns, l) } - if cfg.clientAutoTLS && cfg.clientTLSInfo.Empty() { + if cfg.ClientAutoTLS && cfg.clientTLSInfo.Empty() { var chosts []string for _, u := range cfg.lcurls { chosts = append(chosts, u.Host) } - cfg.clientTLSInfo, err = transport.SelfCert(path.Join(cfg.dir, "fixtures/client"), chosts) + cfg.clientTLSInfo, err = transport.SelfCert(path.Join(cfg.Dir, "fixtures/client"), chosts) if err != nil { plog.Fatalf("could not get certs (%v)", err) } - } else if cfg.clientAutoTLS { + } else if cfg.ClientAutoTLS { plog.Warningf("ignoring client auto TLS since certs given") } @@ -343,26 +343,26 @@ func startEtcd(cfg *config) (<-chan struct{}, error) { } srvcfg := &etcdserver.ServerConfig{ - Name: cfg.name, + Name: cfg.Name, ClientURLs: cfg.acurls, PeerURLs: cfg.apurls, - DataDir: cfg.dir, - DedicatedWALDir: cfg.walDir, - SnapCount: cfg.snapCount, - MaxSnapFiles: cfg.maxSnapFiles, - MaxWALFiles: cfg.maxWalFiles, + DataDir: cfg.Dir, + DedicatedWALDir: cfg.WalDir, + SnapCount: cfg.SnapCount, + MaxSnapFiles: cfg.MaxSnapFiles, + MaxWALFiles: cfg.MaxWalFiles, InitialPeerURLsMap: urlsmap, InitialClusterToken: token, - DiscoveryURL: cfg.durl, - DiscoveryProxy: cfg.dproxy, + DiscoveryURL: cfg.Durl, + DiscoveryProxy: cfg.Dproxy, NewCluster: cfg.isNewCluster(), - ForceNewCluster: cfg.forceNewCluster, + ForceNewCluster: cfg.ForceNewCluster, PeerTLSInfo: cfg.peerTLSInfo, TickMs: cfg.TickMs, ElectionTicks: cfg.electionTicks(), AutoCompactionRetention: cfg.autoCompactionRetention, - QuotaBackendBytes: cfg.quotaBackendBytes, - StrictReconfigCheck: cfg.strictReconfigCheck, + QuotaBackendBytes: cfg.QuotaBackendBytes, + StrictReconfigCheck: cfg.StrictReconfigCheck, EnablePprof: cfg.enablePprof, } var s *etcdserver.EtcdServer @@ -402,33 +402,33 @@ func startEtcd(cfg *config) (<-chan struct{}, error) { // startProxy launches an HTTP proxy for client communication which proxies to other etcd nodes. func startProxy(cfg *config) error { - pt, err := transport.NewTimeoutTransport(cfg.peerTLSInfo, time.Duration(cfg.proxyDialTimeoutMs)*time.Millisecond, time.Duration(cfg.proxyReadTimeoutMs)*time.Millisecond, time.Duration(cfg.proxyWriteTimeoutMs)*time.Millisecond) + pt, err := transport.NewTimeoutTransport(cfg.peerTLSInfo, time.Duration(cfg.ProxyDialTimeoutMs)*time.Millisecond, time.Duration(cfg.ProxyReadTimeoutMs)*time.Millisecond, time.Duration(cfg.ProxyWriteTimeoutMs)*time.Millisecond) if err != nil { return err } pt.MaxIdleConnsPerHost = httpproxy.DefaultMaxIdleConnsPerHost - tr, err := transport.NewTimeoutTransport(cfg.peerTLSInfo, time.Duration(cfg.proxyDialTimeoutMs)*time.Millisecond, time.Duration(cfg.proxyReadTimeoutMs)*time.Millisecond, time.Duration(cfg.proxyWriteTimeoutMs)*time.Millisecond) + tr, err := transport.NewTimeoutTransport(cfg.peerTLSInfo, time.Duration(cfg.ProxyDialTimeoutMs)*time.Millisecond, time.Duration(cfg.ProxyReadTimeoutMs)*time.Millisecond, time.Duration(cfg.ProxyWriteTimeoutMs)*time.Millisecond) if err != nil { return err } - cfg.dir = path.Join(cfg.dir, "proxy") - err = os.MkdirAll(cfg.dir, privateDirMode) + cfg.Dir = path.Join(cfg.Dir, "proxy") + err = os.MkdirAll(cfg.Dir, privateDirMode) if err != nil { return err } var peerURLs []string - clusterfile := path.Join(cfg.dir, "cluster") + clusterfile := path.Join(cfg.Dir, "cluster") b, err := ioutil.ReadFile(clusterfile) switch { case err == nil: - if cfg.durl != "" { + if cfg.Durl != "" { plog.Warningf("discovery token ignored since the proxy has already been initialized. Valid cluster file found at %q", clusterfile) } - if cfg.dnsCluster != "" { + if cfg.DnsCluster != "" { plog.Warningf("DNS SRV discovery ignored since the proxy has already been initialized. Valid cluster file found at %q", clusterfile) } urls := struct{ PeerURLs []string }{} @@ -445,9 +445,9 @@ func startProxy(cfg *config) error { return fmt.Errorf("error setting up initial cluster: %v", err) } - if cfg.durl != "" { + if cfg.Durl != "" { var s string - s, err = discovery.GetCluster(cfg.durl, cfg.dproxy) + s, err = discovery.GetCluster(cfg.Durl, cfg.Dproxy) if err != nil { return err } @@ -499,7 +499,7 @@ func startProxy(cfg *config) error { return clientURLs } - ph := httpproxy.NewHandler(pt, uf, time.Duration(cfg.proxyFailureWaitMs)*time.Millisecond, time.Duration(cfg.proxyRefreshIntervalMs)*time.Millisecond) + ph := httpproxy.NewHandler(pt, uf, time.Duration(cfg.ProxyFailureWaitMs)*time.Millisecond, time.Duration(cfg.ProxyRefreshIntervalMs)*time.Millisecond) ph = &cors.CORSHandler{ Handler: ph, Info: cfg.corsInfo, @@ -541,15 +541,15 @@ func startProxy(cfg *config) error { // getPeerURLsMapAndToken sets up an initial peer URLsMap and cluster token for bootstrap or discovery. func getPeerURLsMapAndToken(cfg *config, which string) (urlsmap types.URLsMap, token string, err error) { switch { - case cfg.durl != "": + case cfg.Durl != "": urlsmap = types.URLsMap{} // If using discovery, generate a temporary cluster based on // self's advertised peer URLs - urlsmap[cfg.name] = cfg.apurls - token = cfg.durl - case cfg.dnsCluster != "": + urlsmap[cfg.Name] = cfg.apurls + token = cfg.Durl + case cfg.DnsCluster != "": var clusterStr string - clusterStr, token, err = discovery.SRVGetCluster(cfg.name, cfg.dnsCluster, cfg.initialClusterToken, cfg.apurls) + clusterStr, token, err = discovery.SRVGetCluster(cfg.Name, cfg.DnsCluster, cfg.InitialClusterToken, cfg.apurls) if err != nil { return nil, "", err } @@ -557,14 +557,14 @@ func getPeerURLsMapAndToken(cfg *config, which string) (urlsmap types.URLsMap, t // only etcd member must belong to the discovered cluster. // proxy does not need to belong to the discovered cluster. if which == "etcd" { - if _, ok := urlsmap[cfg.name]; !ok { - return nil, "", fmt.Errorf("cannot find local etcd member %q in SRV records", cfg.name) + if _, ok := urlsmap[cfg.Name]; !ok { + return nil, "", fmt.Errorf("cannot find local etcd member %q in SRV records", cfg.Name) } } default: // We're statically configured, and cluster has appropriately been set. - urlsmap, err = types.NewURLsMap(cfg.initialCluster) - token = cfg.initialClusterToken + urlsmap, err = types.NewURLsMap(cfg.InitialCluster) + token = cfg.InitialClusterToken } return urlsmap, token, err } @@ -606,12 +606,12 @@ func identifyDataDirOrDie(dir string) dirType { func setupLogging(cfg *config) { capnslog.SetGlobalLogLevel(capnslog.INFO) - if cfg.debug { + if cfg.Debug { capnslog.SetGlobalLogLevel(capnslog.DEBUG) } - if cfg.logPkgLevels != "" { + if cfg.LogPkgLevels != "" { repoLog := capnslog.MustRepoLogger("github.com/coreos/etcd") - settings, err := repoLog.ParseLogLevelConfig(cfg.logPkgLevels) + settings, err := repoLog.ParseLogLevelConfig(cfg.LogPkgLevels) if err != nil { plog.Warningf("couldn't parse log level string: %s, continuing with default levels", err.Error()) return diff --git a/etcdmain/help.go b/etcdmain/help.go index 5fe496108..75437b37d 100644 --- a/etcdmain/help.go +++ b/etcdmain/help.go @@ -25,6 +25,9 @@ var ( etcd -h | --help show the help information about etcd + + etcd --config-file + path to the server configuration file ` flagsline = ` member flags: