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.
This commit is contained in:
Ajit Yagaty 2016-05-05 00:47:38 -07:00
parent 3bcd2b5b9f
commit 8bc5ab9f8d
5 changed files with 707 additions and 184 deletions

View File

@ -19,6 +19,7 @@ package etcdmain
import ( import (
"flag" "flag"
"fmt" "fmt"
"io/ioutil"
"net/url" "net/url"
"os" "os"
"runtime" "runtime"
@ -28,7 +29,9 @@ import (
"github.com/coreos/etcd/pkg/cors" "github.com/coreos/etcd/pkg/cors"
"github.com/coreos/etcd/pkg/flags" "github.com/coreos/etcd/pkg/flags"
"github.com/coreos/etcd/pkg/transport" "github.com/coreos/etcd/pkg/transport"
"github.com/coreos/etcd/pkg/types"
"github.com/coreos/etcd/version" "github.com/coreos/etcd/version"
"github.com/ghodss/yaml"
) )
const ( const (
@ -44,6 +47,9 @@ const (
defaultName = "default" defaultName = "default"
defaultInitialAdvertisePeerURLs = "http://localhost:2380,http://localhost:7001" 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. // maxElectionMs specifies the maximum value of election timeout.
// More details are listed in ../Documentation/tuning.md#time-parameters. // More details are listed in ../Documentation/tuning.md#time-parameters.
@ -77,49 +83,61 @@ type config struct {
// member // member
corsInfo *cors.CORSInfo corsInfo *cors.CORSInfo
dir string
walDir string
lpurls, lcurls []url.URL lpurls, lcurls []url.URL
maxSnapFiles uint Dir string `json:"data-dir"`
maxWalFiles uint WalDir string `json:"wal-dir"`
name string MaxSnapFiles uint `json:"max-snapshots"`
snapCount uint64 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. // TickMs is the number of milliseconds between heartbeat ticks.
// TODO: decouple tickMs and heartbeat tick (current heartbeat tick = 1). // TODO: decouple tickMs and heartbeat tick (current heartbeat tick = 1).
// make ticks a cluster wide configuration. // make ticks a cluster wide configuration.
TickMs uint TickMs uint `json:"heartbeat-interval"`
ElectionMs uint ElectionMs uint `json:"election-timeout"`
quotaBackendBytes int64 QuotaBackendBytes int64 `json:"quota-backend-bytes"`
// clustering // clustering
apurls, acurls []url.URL apurls, acurls []url.URL
clusterState *flags.StringsFlag clusterState *flags.StringsFlag
dnsCluster string DnsCluster string `json:"discovery-srv"`
dproxy string Dproxy string `json:"discovery-proxy"`
durl string Durl string `json:"discovery"`
fallback *flags.StringsFlag fallback *flags.StringsFlag
initialCluster string InitialCluster string `json:"initial-cluster"`
initialClusterToken string InitialClusterToken string `json:"initial-cluster-token"`
strictReconfigCheck bool 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
proxy *flags.StringsFlag proxy *flags.StringsFlag
proxyFailureWaitMs uint ProxyFailureWaitMs uint `json:"proxy-failure-wait"`
proxyRefreshIntervalMs uint ProxyRefreshIntervalMs uint `json:"proxy-refresh-interval"`
proxyDialTimeoutMs uint ProxyDialTimeoutMs uint `json:"proxy-dial-timeout"`
proxyWriteTimeoutMs uint ProxyWriteTimeoutMs uint `json:"proxy-write-timeout"`
proxyReadTimeoutMs uint ProxyReadTimeoutMs uint `json:"proxy-read-timeout"`
ProxyCfgFile string `json:"proxy"`
// security // security
clientTLSInfo, peerTLSInfo transport.TLSInfo 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 logging
debug bool Debug bool `json:"debug"`
logPkgLevels string LogPkgLevels string `json:"log-package-levels"`
// unsafe // ForceNewCluster is unsafe
forceNewCluster bool ForceNewCluster bool `json:"force-new-cluster"`
printVersion bool printVersion bool
@ -127,9 +145,20 @@ type config struct {
enablePprof bool enablePprof bool
configFile string
ignored []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 { func NewConfig() *config {
cfg := &config{ cfg := &config{
corsInfo: &cors.CORSInfo{}, corsInfo: &cors.CORSInfo{},
@ -155,39 +184,41 @@ func NewConfig() *config {
fmt.Println(usageline) fmt.Println(usageline)
} }
fs.StringVar(&cfg.configFile, "config-file", "", "Path to the server configuration file")
// member // member
fs.Var(cfg.corsInfo, "cors", "Comma-separated white list of origins for CORS (cross-origin resource sharing).") 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.Dir, "data-dir", "", "Path to the data directory.")
fs.StringVar(&cfg.walDir, "wal-dir", "", "Path to the dedicated wal 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(defaultListenPeerURLs), "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.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.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.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.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.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.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.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 // 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(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.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.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, ", "))) 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 { if err := cfg.fallback.Set(fallbackFlagProxy); err != nil {
// Should never happen. // Should never happen.
plog.Panicf("unexpected error setting up discovery-fallback flag: %v", err) 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.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.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.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.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').") fs.Var(cfg.clusterState, "initial-cluster-state", "Initial cluster state ('new' or 'existing').")
if err := cfg.clusterState.Set(clusterStateFlagNew); err != nil { if err := cfg.clusterState.Set(clusterStateFlagNew); err != nil {
// Should never happen. // Should never happen.
plog.Panicf("unexpected error setting up clusterStateFlag: %v", err) 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 // proxy
fs.Var(cfg.proxy, "proxy", fmt.Sprintf("Valid values include %s", strings.Join(cfg.proxy.Values, ", "))) 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. // Should never happen.
plog.Panicf("unexpected error setting up proxyFlag: %v", err) 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.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.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.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.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.ProxyReadTimeoutMs, "proxy-read-timeout", 0, "Time (in milliseconds) for a read to timeout.")
// security // security
fs.StringVar(&cfg.clientTLSInfo.CAFile, "ca-file", "", "DEPRECATED: Path to the client server TLS CA file.") 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.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.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.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.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.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.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.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.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 // logging
fs.BoolVar(&cfg.debug, "debug", false, "Enable debug-level logging for etcd.") 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.StringVar(&cfg.LogPkgLevels, "log-package-levels", "", "Specify a particular log level for each etcd package (eg: 'etcdmain=CRITICAL,etcdserver=DEBUG').")
// unsafe // 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 // version
fs.BoolVar(&cfg.printVersion, "version", false, "Print the version and exit.") 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) 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) err := flags.SetFlagsFromEnv("ETCD", cfg.FlagSet)
if err != nil { if err != nil {
plog.Fatalf("%v", err) 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, "peer-bind-addr", "peer-addr")
flags.SetBindAddrFromAddr(cfg.FlagSet, "bind-addr", "addr") flags.SetBindAddrFromAddr(cfg.FlagSet, "bind-addr", "addr")
@ -307,16 +336,126 @@ func (cfg *config) Parse(arguments []string) error {
return err 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. // 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 // 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 mayBeProxy := cfg.proxy.String() != proxyFlagOff || mayFallbackToProxy
if !mayBeProxy { 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 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 { 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) return fmt.Errorf("--election-timeout[%vms] should be at least as 5 times as --heartbeat-interval[%vms]", cfg.ElectionMs, cfg.TickMs)
} }

View File

@ -15,9 +15,15 @@
package etcdmain package etcdmain
import ( import (
"fmt"
"io/ioutil"
"net/url" "net/url"
"os"
"reflect" "reflect"
"strings"
"testing" "testing"
"github.com/ghodss/yaml"
) )
func TestConfigParsingMemberFlags(t *testing.T) { func TestConfigParsingMemberFlags(t *testing.T) {
@ -32,42 +38,56 @@ func TestConfigParsingMemberFlags(t *testing.T) {
// it should be set if -listen-client-urls is set // it should be set if -listen-client-urls is set
"-advertise-client-urls=http://localhost:7000,https://localhost:7001", "-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() cfg := NewConfig()
err := cfg.Parse(args) err := cfg.Parse(args)
if err != nil { if err != nil {
t.Fatal(err) 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) cfg := NewConfig()
} err = cfg.Parse(args)
if cfg.snapCount != wcfg.snapCount { if err != nil {
t.Errorf("snapcount = %v, want %v", cfg.snapCount, wcfg.snapCount) t.Fatal(err)
}
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)
} }
validateMemberFlags(t, cfg)
} }
func TestConfigParsingClusteringFlags(t *testing.T) { func TestConfigParsingClusteringFlags(t *testing.T) {
@ -79,37 +99,51 @@ func TestConfigParsingClusteringFlags(t *testing.T) {
"-advertise-client-urls=http://localhost:7000,https://localhost:7001", "-advertise-client-urls=http://localhost:7000,https://localhost:7001",
"-discovery-fallback=exit", "-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() cfg := NewConfig()
err := cfg.Parse(args) err := cfg.Parse(args)
if err != nil { if err != nil {
t.Fatal(err) 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 { cfg := NewConfig()
t.Errorf("initialClusterToken = %v, want %v", cfg.initialClusterToken, wcfg.initialClusterToken) err = cfg.Parse(args)
} if err != nil {
if !reflect.DeepEqual(cfg.apurls, wcfg.apurls) { t.Fatal(err)
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)
} }
validateClusteringFlags(t, cfg)
} }
func TestConfigParsingOtherFlags(t *testing.T) { func TestConfigParsingOtherFlags(t *testing.T) {
@ -124,33 +158,55 @@ func TestConfigParsingOtherFlags(t *testing.T) {
"-force-new-cluster=true", "-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() cfg := NewConfig()
err := cfg.Parse(args) err := cfg.Parse(args)
if err != nil { if err != nil {
t.Fatal(err) 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) { 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) { func TestConfigParsingMissedAdvertiseClientURLsFlag(t *testing.T) {
tests := []struct { tests := []struct {
args []string 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)
}
}

View File

@ -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

View File

@ -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()) 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 // TODO: check whether fields are set instead of whether fields have default value
if cfg.name != defaultName && cfg.initialCluster == initialClusterFromName(defaultName) { if cfg.Name != defaultName && cfg.InitialCluster == initialClusterFromName(defaultName) {
cfg.initialCluster = initialClusterFromName(cfg.name) cfg.InitialCluster = initialClusterFromName(cfg.Name)
} }
if cfg.dir == "" { if cfg.Dir == "" {
cfg.dir = fmt.Sprintf("%v.etcd", cfg.name) cfg.Dir = fmt.Sprintf("%v.etcd", cfg.Name)
plog.Warningf("no data-dir provided, using default data-dir ./%s", cfg.dir) plog.Warningf("no data-dir provided, using default data-dir ./%s", cfg.Dir)
} }
which := identifyDataDirOrDie(cfg.dir) which := identifyDataDirOrDie(cfg.Dir)
if which != dirEmpty { if which != dirEmpty {
plog.Noticef("the server is already initialized as %v before, starting as etcd %v...", which, which) plog.Noticef("the server is already initialized as %v before, starting as etcd %v...", which, which)
switch which { switch which {
@ -141,17 +141,17 @@ func Main() {
if derr, ok := err.(*etcdserver.DiscoveryError); ok { if derr, ok := err.(*etcdserver.DiscoveryError); ok {
switch derr.Err { switch derr.Err {
case discovery.ErrDuplicateID: case discovery.ErrDuplicateID:
plog.Errorf("member %q has previously registered with discovery service token (%s).", cfg.name, cfg.durl) 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("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("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.") plog.Infof("or use a new discovery token if the previous bootstrap failed.")
case discovery.ErrDuplicateName: 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 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.") plog.Errorf("please do not reuse the discovery token and generate a new one to bootstrap the cluster.")
default: default:
plog.Errorf("%v", err) 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.") plog.Infof("please generate a new discovery token and try to bootstrap again.")
} }
os.Exit(1) os.Exit(1)
@ -159,13 +159,13 @@ func Main() {
if strings.Contains(err.Error(), "include") && strings.Contains(err.Error(), "--initial-cluster") { if strings.Contains(err.Error(), "include") && strings.Contains(err.Error(), "--initial-cluster") {
plog.Infof("%v", err) plog.Infof("%v", err)
if cfg.initialCluster == initialClusterFromName(cfg.name) { if cfg.InitialCluster == initialClusterFromName(cfg.Name) {
plog.Infof("forgot to set --initial-cluster flag?") plog.Infof("forgot to set --initial-cluster flag?")
} }
if types.URLs(cfg.apurls).String() == defaultInitialAdvertisePeerURLs { if types.URLs(cfg.apurls).String() == defaultInitialAdvertisePeerURLs {
plog.Infof("forgot to set --initial-advertise-peer-urls flag?") 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.") plog.Infof("if you want to use discovery service, please set --discovery flag.")
} }
os.Exit(1) 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) 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 var phosts []string
for _, u := range cfg.lpurls { for _, u := range cfg.lpurls {
phosts = append(phosts, u.Host) 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 { if err != nil {
plog.Fatalf("could not get certs (%v)", err) 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") plog.Warningf("ignoring peer auto TLS since certs given")
} }
@ -257,16 +257,16 @@ func startEtcd(cfg *config) (<-chan struct{}, error) {
plns = append(plns, l) plns = append(plns, l)
} }
if cfg.clientAutoTLS && cfg.clientTLSInfo.Empty() { if cfg.ClientAutoTLS && cfg.clientTLSInfo.Empty() {
var chosts []string var chosts []string
for _, u := range cfg.lcurls { for _, u := range cfg.lcurls {
chosts = append(chosts, u.Host) 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 { if err != nil {
plog.Fatalf("could not get certs (%v)", err) 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") plog.Warningf("ignoring client auto TLS since certs given")
} }
@ -343,26 +343,26 @@ func startEtcd(cfg *config) (<-chan struct{}, error) {
} }
srvcfg := &etcdserver.ServerConfig{ srvcfg := &etcdserver.ServerConfig{
Name: cfg.name, Name: cfg.Name,
ClientURLs: cfg.acurls, ClientURLs: cfg.acurls,
PeerURLs: cfg.apurls, PeerURLs: cfg.apurls,
DataDir: cfg.dir, DataDir: cfg.Dir,
DedicatedWALDir: cfg.walDir, DedicatedWALDir: cfg.WalDir,
SnapCount: cfg.snapCount, SnapCount: cfg.SnapCount,
MaxSnapFiles: cfg.maxSnapFiles, MaxSnapFiles: cfg.MaxSnapFiles,
MaxWALFiles: cfg.maxWalFiles, MaxWALFiles: cfg.MaxWalFiles,
InitialPeerURLsMap: urlsmap, InitialPeerURLsMap: urlsmap,
InitialClusterToken: token, InitialClusterToken: token,
DiscoveryURL: cfg.durl, DiscoveryURL: cfg.Durl,
DiscoveryProxy: cfg.dproxy, DiscoveryProxy: cfg.Dproxy,
NewCluster: cfg.isNewCluster(), NewCluster: cfg.isNewCluster(),
ForceNewCluster: cfg.forceNewCluster, ForceNewCluster: cfg.ForceNewCluster,
PeerTLSInfo: cfg.peerTLSInfo, PeerTLSInfo: cfg.peerTLSInfo,
TickMs: cfg.TickMs, TickMs: cfg.TickMs,
ElectionTicks: cfg.electionTicks(), ElectionTicks: cfg.electionTicks(),
AutoCompactionRetention: cfg.autoCompactionRetention, AutoCompactionRetention: cfg.autoCompactionRetention,
QuotaBackendBytes: cfg.quotaBackendBytes, QuotaBackendBytes: cfg.QuotaBackendBytes,
StrictReconfigCheck: cfg.strictReconfigCheck, StrictReconfigCheck: cfg.StrictReconfigCheck,
EnablePprof: cfg.enablePprof, EnablePprof: cfg.enablePprof,
} }
var s *etcdserver.EtcdServer 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. // startProxy launches an HTTP proxy for client communication which proxies to other etcd nodes.
func startProxy(cfg *config) error { 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 { if err != nil {
return err return err
} }
pt.MaxIdleConnsPerHost = httpproxy.DefaultMaxIdleConnsPerHost 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 { if err != nil {
return err return err
} }
cfg.dir = path.Join(cfg.dir, "proxy") cfg.Dir = path.Join(cfg.Dir, "proxy")
err = os.MkdirAll(cfg.dir, privateDirMode) err = os.MkdirAll(cfg.Dir, privateDirMode)
if err != nil { if err != nil {
return err return err
} }
var peerURLs []string var peerURLs []string
clusterfile := path.Join(cfg.dir, "cluster") clusterfile := path.Join(cfg.Dir, "cluster")
b, err := ioutil.ReadFile(clusterfile) b, err := ioutil.ReadFile(clusterfile)
switch { switch {
case err == nil: 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) 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) plog.Warningf("DNS SRV discovery ignored since the proxy has already been initialized. Valid cluster file found at %q", clusterfile)
} }
urls := struct{ PeerURLs []string }{} urls := struct{ PeerURLs []string }{}
@ -445,9 +445,9 @@ func startProxy(cfg *config) error {
return fmt.Errorf("error setting up initial cluster: %v", err) return fmt.Errorf("error setting up initial cluster: %v", err)
} }
if cfg.durl != "" { if cfg.Durl != "" {
var s string var s string
s, err = discovery.GetCluster(cfg.durl, cfg.dproxy) s, err = discovery.GetCluster(cfg.Durl, cfg.Dproxy)
if err != nil { if err != nil {
return err return err
} }
@ -499,7 +499,7 @@ func startProxy(cfg *config) error {
return clientURLs 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{ ph = &cors.CORSHandler{
Handler: ph, Handler: ph,
Info: cfg.corsInfo, 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. // 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) { func getPeerURLsMapAndToken(cfg *config, which string) (urlsmap types.URLsMap, token string, err error) {
switch { switch {
case cfg.durl != "": case cfg.Durl != "":
urlsmap = types.URLsMap{} urlsmap = types.URLsMap{}
// If using discovery, generate a temporary cluster based on // If using discovery, generate a temporary cluster based on
// self's advertised peer URLs // self's advertised peer URLs
urlsmap[cfg.name] = cfg.apurls urlsmap[cfg.Name] = cfg.apurls
token = cfg.durl token = cfg.Durl
case cfg.dnsCluster != "": case cfg.DnsCluster != "":
var clusterStr string 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 { if err != nil {
return nil, "", err 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. // only etcd member must belong to the discovered cluster.
// proxy does not need to belong to the discovered cluster. // proxy does not need to belong to the discovered cluster.
if which == "etcd" { if which == "etcd" {
if _, ok := urlsmap[cfg.name]; !ok { if _, ok := urlsmap[cfg.Name]; !ok {
return nil, "", fmt.Errorf("cannot find local etcd member %q in SRV records", cfg.name) return nil, "", fmt.Errorf("cannot find local etcd member %q in SRV records", cfg.Name)
} }
} }
default: default:
// We're statically configured, and cluster has appropriately been set. // We're statically configured, and cluster has appropriately been set.
urlsmap, err = types.NewURLsMap(cfg.initialCluster) urlsmap, err = types.NewURLsMap(cfg.InitialCluster)
token = cfg.initialClusterToken token = cfg.InitialClusterToken
} }
return urlsmap, token, err return urlsmap, token, err
} }
@ -606,12 +606,12 @@ func identifyDataDirOrDie(dir string) dirType {
func setupLogging(cfg *config) { func setupLogging(cfg *config) {
capnslog.SetGlobalLogLevel(capnslog.INFO) capnslog.SetGlobalLogLevel(capnslog.INFO)
if cfg.debug { if cfg.Debug {
capnslog.SetGlobalLogLevel(capnslog.DEBUG) capnslog.SetGlobalLogLevel(capnslog.DEBUG)
} }
if cfg.logPkgLevels != "" { if cfg.LogPkgLevels != "" {
repoLog := capnslog.MustRepoLogger("github.com/coreos/etcd") repoLog := capnslog.MustRepoLogger("github.com/coreos/etcd")
settings, err := repoLog.ParseLogLevelConfig(cfg.logPkgLevels) settings, err := repoLog.ParseLogLevelConfig(cfg.LogPkgLevels)
if err != nil { if err != nil {
plog.Warningf("couldn't parse log level string: %s, continuing with default levels", err.Error()) plog.Warningf("couldn't parse log level string: %s, continuing with default levels", err.Error())
return return

View File

@ -25,6 +25,9 @@ var (
etcd -h | --help etcd -h | --help
show the help information about etcd show the help information about etcd
etcd --config-file
path to the server configuration file
` `
flagsline = ` flagsline = `
member flags: member flags: