mirror of
https://github.com/etcd-io/etcd.git
synced 2024-09-27 06:25:44 +00:00
server: Move server files to 'server' directory.
26 git mv mvcc wal auth etcdserver etcdmain proxy embed/ lease/ server 36 git mv go.mod go.sum server
This commit is contained in:
417
server/etcdmain/config.go
Normal file
417
server/etcdmain/config.go
Normal file
@@ -0,0 +1,417 @@
|
||||
// Copyright 2015 The etcd Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Every change should be reflected on help.go as well.
|
||||
|
||||
package etcdmain
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"runtime"
|
||||
|
||||
"go.etcd.io/etcd/api/v3/version"
|
||||
"go.etcd.io/etcd/pkg/v3/flags"
|
||||
"go.etcd.io/etcd/pkg/v3/logutil"
|
||||
"go.etcd.io/etcd/v3/embed"
|
||||
|
||||
"go.uber.org/zap"
|
||||
"sigs.k8s.io/yaml"
|
||||
)
|
||||
|
||||
var (
|
||||
proxyFlagOff = "off"
|
||||
proxyFlagReadonly = "readonly"
|
||||
proxyFlagOn = "on"
|
||||
|
||||
fallbackFlagExit = "exit"
|
||||
fallbackFlagProxy = "proxy"
|
||||
|
||||
ignored = []string{
|
||||
"cluster-active-size",
|
||||
"cluster-remove-delay",
|
||||
"cluster-sync-interval",
|
||||
"config",
|
||||
"force",
|
||||
"max-result-buffer",
|
||||
"max-retry-attempts",
|
||||
"peer-heartbeat-interval",
|
||||
"peer-election-timeout",
|
||||
"retry-interval",
|
||||
"snapshot",
|
||||
"v",
|
||||
"vv",
|
||||
// for coverage testing
|
||||
"test.coverprofile",
|
||||
"test.outputdir",
|
||||
}
|
||||
)
|
||||
|
||||
type configProxy struct {
|
||||
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"`
|
||||
Fallback string
|
||||
Proxy string
|
||||
ProxyJSON string `json:"proxy"`
|
||||
FallbackJSON string `json:"discovery-fallback"`
|
||||
}
|
||||
|
||||
// config holds the config for a command line invocation of etcd
|
||||
type config struct {
|
||||
ec embed.Config
|
||||
cp configProxy
|
||||
cf configFlags
|
||||
configFile string
|
||||
printVersion bool
|
||||
ignored []string
|
||||
}
|
||||
|
||||
// configFlags has the set of flags used for command line parsing a Config
|
||||
type configFlags struct {
|
||||
flagSet *flag.FlagSet
|
||||
clusterState *flags.SelectiveStringValue
|
||||
fallback *flags.SelectiveStringValue
|
||||
proxy *flags.SelectiveStringValue
|
||||
}
|
||||
|
||||
func newConfig() *config {
|
||||
cfg := &config{
|
||||
ec: *embed.NewConfig(),
|
||||
cp: configProxy{
|
||||
Proxy: proxyFlagOff,
|
||||
ProxyFailureWaitMs: 5000,
|
||||
ProxyRefreshIntervalMs: 30000,
|
||||
ProxyDialTimeoutMs: 1000,
|
||||
ProxyWriteTimeoutMs: 5000,
|
||||
},
|
||||
ignored: ignored,
|
||||
}
|
||||
cfg.cf = configFlags{
|
||||
flagSet: flag.NewFlagSet("etcd", flag.ContinueOnError),
|
||||
clusterState: flags.NewSelectiveStringValue(
|
||||
embed.ClusterStateFlagNew,
|
||||
embed.ClusterStateFlagExisting,
|
||||
),
|
||||
fallback: flags.NewSelectiveStringValue(
|
||||
fallbackFlagProxy,
|
||||
fallbackFlagExit,
|
||||
),
|
||||
proxy: flags.NewSelectiveStringValue(
|
||||
proxyFlagOff,
|
||||
proxyFlagReadonly,
|
||||
proxyFlagOn,
|
||||
),
|
||||
}
|
||||
|
||||
fs := cfg.cf.flagSet
|
||||
fs.Usage = func() {
|
||||
fmt.Fprintln(os.Stderr, usageline)
|
||||
}
|
||||
|
||||
fs.StringVar(&cfg.configFile, "config-file", "", "Path to the server configuration file. Note that if a configuration file is provided, other command line flags and environment variables will be ignored.")
|
||||
|
||||
// member
|
||||
fs.StringVar(&cfg.ec.Dir, "data-dir", cfg.ec.Dir, "Path to the data directory.")
|
||||
fs.StringVar(&cfg.ec.WalDir, "wal-dir", cfg.ec.WalDir, "Path to the dedicated wal directory.")
|
||||
fs.Var(
|
||||
flags.NewUniqueURLsWithExceptions(embed.DefaultListenPeerURLs, ""),
|
||||
"listen-peer-urls",
|
||||
"List of URLs to listen on for peer traffic.",
|
||||
)
|
||||
fs.Var(
|
||||
flags.NewUniqueURLsWithExceptions(embed.DefaultListenClientURLs, ""), "listen-client-urls",
|
||||
"List of URLs to listen on for client traffic.",
|
||||
)
|
||||
fs.Var(
|
||||
flags.NewUniqueURLsWithExceptions("", ""),
|
||||
"listen-metrics-urls",
|
||||
"List of URLs to listen on for the metrics and health endpoints.",
|
||||
)
|
||||
fs.UintVar(&cfg.ec.MaxSnapFiles, "max-snapshots", cfg.ec.MaxSnapFiles, "Maximum number of snapshot files to retain (0 is unlimited).")
|
||||
fs.UintVar(&cfg.ec.MaxWalFiles, "max-wals", cfg.ec.MaxWalFiles, "Maximum number of wal files to retain (0 is unlimited).")
|
||||
fs.StringVar(&cfg.ec.Name, "name", cfg.ec.Name, "Human-readable name for this member.")
|
||||
fs.Uint64Var(&cfg.ec.SnapshotCount, "snapshot-count", cfg.ec.SnapshotCount, "Number of committed transactions to trigger a snapshot to disk.")
|
||||
fs.UintVar(&cfg.ec.TickMs, "heartbeat-interval", cfg.ec.TickMs, "Time (in milliseconds) of a heartbeat interval.")
|
||||
fs.UintVar(&cfg.ec.ElectionMs, "election-timeout", cfg.ec.ElectionMs, "Time (in milliseconds) for an election to timeout.")
|
||||
fs.BoolVar(&cfg.ec.InitialElectionTickAdvance, "initial-election-tick-advance", cfg.ec.InitialElectionTickAdvance, "Whether to fast-forward initial election ticks on boot for faster election.")
|
||||
fs.Int64Var(&cfg.ec.QuotaBackendBytes, "quota-backend-bytes", cfg.ec.QuotaBackendBytes, "Raise alarms when backend size exceeds the given quota. 0 means use the default quota.")
|
||||
fs.StringVar(&cfg.ec.BackendFreelistType, "backend-bbolt-freelist-type", cfg.ec.BackendFreelistType, "BackendFreelistType specifies the type of freelist that boltdb backend uses(array and map are supported types)")
|
||||
fs.DurationVar(&cfg.ec.BackendBatchInterval, "backend-batch-interval", cfg.ec.BackendBatchInterval, "BackendBatchInterval is the maximum time before commit the backend transaction.")
|
||||
fs.IntVar(&cfg.ec.BackendBatchLimit, "backend-batch-limit", cfg.ec.BackendBatchLimit, "BackendBatchLimit is the maximum operations before commit the backend transaction.")
|
||||
fs.UintVar(&cfg.ec.MaxTxnOps, "max-txn-ops", cfg.ec.MaxTxnOps, "Maximum number of operations permitted in a transaction.")
|
||||
fs.UintVar(&cfg.ec.MaxRequestBytes, "max-request-bytes", cfg.ec.MaxRequestBytes, "Maximum client request size in bytes the server will accept.")
|
||||
fs.DurationVar(&cfg.ec.GRPCKeepAliveMinTime, "grpc-keepalive-min-time", cfg.ec.GRPCKeepAliveMinTime, "Minimum interval duration that a client should wait before pinging server.")
|
||||
fs.DurationVar(&cfg.ec.GRPCKeepAliveInterval, "grpc-keepalive-interval", cfg.ec.GRPCKeepAliveInterval, "Frequency duration of server-to-client ping to check if a connection is alive (0 to disable).")
|
||||
fs.DurationVar(&cfg.ec.GRPCKeepAliveTimeout, "grpc-keepalive-timeout", cfg.ec.GRPCKeepAliveTimeout, "Additional duration of wait before closing a non-responsive connection (0 to disable).")
|
||||
|
||||
// clustering
|
||||
fs.Var(
|
||||
flags.NewUniqueURLsWithExceptions(embed.DefaultInitialAdvertisePeerURLs, ""),
|
||||
"initial-advertise-peer-urls",
|
||||
"List of this member's peer URLs to advertise to the rest of the cluster.",
|
||||
)
|
||||
fs.Var(
|
||||
flags.NewUniqueURLsWithExceptions(embed.DefaultAdvertiseClientURLs, ""),
|
||||
"advertise-client-urls",
|
||||
"List of this member's client URLs to advertise to the public.",
|
||||
)
|
||||
fs.StringVar(&cfg.ec.Durl, "discovery", cfg.ec.Durl, "Discovery URL used to bootstrap the cluster.")
|
||||
fs.Var(cfg.cf.fallback, "discovery-fallback", fmt.Sprintf("Valid values include %q", cfg.cf.fallback.Valids()))
|
||||
|
||||
fs.StringVar(&cfg.ec.Dproxy, "discovery-proxy", cfg.ec.Dproxy, "HTTP proxy to use for traffic to discovery service.")
|
||||
fs.StringVar(&cfg.ec.DNSCluster, "discovery-srv", cfg.ec.DNSCluster, "DNS domain used to bootstrap initial cluster.")
|
||||
fs.StringVar(&cfg.ec.DNSClusterServiceName, "discovery-srv-name", cfg.ec.DNSClusterServiceName, "Service name to query when using DNS discovery.")
|
||||
fs.StringVar(&cfg.ec.InitialCluster, "initial-cluster", cfg.ec.InitialCluster, "Initial cluster configuration for bootstrapping.")
|
||||
fs.StringVar(&cfg.ec.InitialClusterToken, "initial-cluster-token", cfg.ec.InitialClusterToken, "Initial cluster token for the etcd cluster during bootstrap.")
|
||||
fs.Var(cfg.cf.clusterState, "initial-cluster-state", "Initial cluster state ('new' or 'existing').")
|
||||
|
||||
fs.BoolVar(&cfg.ec.StrictReconfigCheck, "strict-reconfig-check", cfg.ec.StrictReconfigCheck, "Reject reconfiguration requests that would cause quorum loss.")
|
||||
fs.BoolVar(&cfg.ec.EnableV2, "enable-v2", cfg.ec.EnableV2, "Accept etcd V2 client requests.")
|
||||
fs.BoolVar(&cfg.ec.PreVote, "pre-vote", cfg.ec.PreVote, "Enable to run an additional Raft election phase.")
|
||||
|
||||
// proxy
|
||||
fs.Var(cfg.cf.proxy, "proxy", fmt.Sprintf("Valid values include %q", cfg.cf.proxy.Valids()))
|
||||
fs.UintVar(&cfg.cp.ProxyFailureWaitMs, "proxy-failure-wait", cfg.cp.ProxyFailureWaitMs, "Time (in milliseconds) an endpoint will be held in a failed state.")
|
||||
fs.UintVar(&cfg.cp.ProxyRefreshIntervalMs, "proxy-refresh-interval", cfg.cp.ProxyRefreshIntervalMs, "Time (in milliseconds) of the endpoints refresh interval.")
|
||||
fs.UintVar(&cfg.cp.ProxyDialTimeoutMs, "proxy-dial-timeout", cfg.cp.ProxyDialTimeoutMs, "Time (in milliseconds) for a dial to timeout.")
|
||||
fs.UintVar(&cfg.cp.ProxyWriteTimeoutMs, "proxy-write-timeout", cfg.cp.ProxyWriteTimeoutMs, "Time (in milliseconds) for a write to timeout.")
|
||||
fs.UintVar(&cfg.cp.ProxyReadTimeoutMs, "proxy-read-timeout", cfg.cp.ProxyReadTimeoutMs, "Time (in milliseconds) for a read to timeout.")
|
||||
|
||||
// security
|
||||
fs.StringVar(&cfg.ec.ClientTLSInfo.CertFile, "cert-file", "", "Path to the client server TLS cert file.")
|
||||
fs.StringVar(&cfg.ec.ClientTLSInfo.KeyFile, "key-file", "", "Path to the client server TLS key file.")
|
||||
fs.BoolVar(&cfg.ec.ClientTLSInfo.ClientCertAuth, "client-cert-auth", false, "Enable client cert authentication.")
|
||||
fs.StringVar(&cfg.ec.ClientTLSInfo.CRLFile, "client-crl-file", "", "Path to the client certificate revocation list file.")
|
||||
fs.StringVar(&cfg.ec.ClientTLSInfo.AllowedHostname, "client-cert-allowed-hostname", "", "Allowed TLS hostname for client cert authentication.")
|
||||
fs.StringVar(&cfg.ec.ClientTLSInfo.TrustedCAFile, "trusted-ca-file", "", "Path to the client server TLS trusted CA cert file.")
|
||||
fs.BoolVar(&cfg.ec.ClientAutoTLS, "auto-tls", false, "Client TLS using generated certificates")
|
||||
fs.StringVar(&cfg.ec.PeerTLSInfo.CertFile, "peer-cert-file", "", "Path to the peer server TLS cert file.")
|
||||
fs.StringVar(&cfg.ec.PeerTLSInfo.KeyFile, "peer-key-file", "", "Path to the peer server TLS key file.")
|
||||
fs.BoolVar(&cfg.ec.PeerTLSInfo.ClientCertAuth, "peer-client-cert-auth", false, "Enable peer client cert authentication.")
|
||||
fs.StringVar(&cfg.ec.PeerTLSInfo.TrustedCAFile, "peer-trusted-ca-file", "", "Path to the peer server TLS trusted CA file.")
|
||||
fs.BoolVar(&cfg.ec.PeerAutoTLS, "peer-auto-tls", false, "Peer TLS using generated certificates")
|
||||
fs.StringVar(&cfg.ec.PeerTLSInfo.CRLFile, "peer-crl-file", "", "Path to the peer certificate revocation list file.")
|
||||
fs.StringVar(&cfg.ec.PeerTLSInfo.AllowedCN, "peer-cert-allowed-cn", "", "Allowed CN for inter peer authentication.")
|
||||
fs.StringVar(&cfg.ec.PeerTLSInfo.AllowedHostname, "peer-cert-allowed-hostname", "", "Allowed TLS hostname for inter peer authentication.")
|
||||
fs.Var(flags.NewStringsValue(""), "cipher-suites", "Comma-separated list of supported TLS cipher suites between client/server and peers (empty will be auto-populated by Go).")
|
||||
fs.BoolVar(&cfg.ec.PeerTLSInfo.SkipClientSANVerify, "experimental-peer-skip-client-san-verification", false, "Skip verification of SAN field in client certificate for peer connections.")
|
||||
|
||||
fs.Var(
|
||||
flags.NewUniqueURLsWithExceptions("*", "*"),
|
||||
"cors",
|
||||
"Comma-separated white list of origins for CORS, or cross-origin resource sharing, (empty or * means allow all)",
|
||||
)
|
||||
fs.Var(flags.NewUniqueStringsValue("*"), "host-whitelist", "Comma-separated acceptable hostnames from HTTP client requests, if server is not secure (empty means allow all).")
|
||||
|
||||
// logging
|
||||
fs.StringVar(&cfg.ec.Logger, "logger", "zap", "Currently only supports 'zap' for structured logging.")
|
||||
fs.Var(flags.NewUniqueStringsValue(embed.DefaultLogOutput), "log-outputs", "Specify 'stdout' or 'stderr' to skip journald logging even when running under systemd, or list of comma separated output targets.")
|
||||
fs.StringVar(&cfg.ec.LogLevel, "log-level", logutil.DefaultLogLevel, "Configures log level. Only supports debug, info, warn, error, panic, or fatal. Default 'info'.")
|
||||
|
||||
// version
|
||||
fs.BoolVar(&cfg.printVersion, "version", false, "Print the version and exit.")
|
||||
|
||||
fs.StringVar(&cfg.ec.AutoCompactionRetention, "auto-compaction-retention", "0", "Auto compaction retention for mvcc key value store. 0 means disable auto compaction.")
|
||||
fs.StringVar(&cfg.ec.AutoCompactionMode, "auto-compaction-mode", "periodic", "interpret 'auto-compaction-retention' one of: periodic|revision. 'periodic' for duration based retention, defaulting to hours if no time unit is provided (e.g. '5m'). 'revision' for revision number based retention.")
|
||||
|
||||
// pprof profiler via HTTP
|
||||
fs.BoolVar(&cfg.ec.EnablePprof, "enable-pprof", false, "Enable runtime profiling data via HTTP server. Address is at client URL + \"/debug/pprof/\"")
|
||||
|
||||
// additional metrics
|
||||
fs.StringVar(&cfg.ec.Metrics, "metrics", cfg.ec.Metrics, "Set level of detail for exported metrics, specify 'extensive' to include server side grpc histogram metrics")
|
||||
|
||||
// auth
|
||||
fs.StringVar(&cfg.ec.AuthToken, "auth-token", cfg.ec.AuthToken, "Specify auth token specific options.")
|
||||
fs.UintVar(&cfg.ec.BcryptCost, "bcrypt-cost", cfg.ec.BcryptCost, "Specify bcrypt algorithm cost factor for auth password hashing.")
|
||||
fs.UintVar(&cfg.ec.AuthTokenTTL, "auth-token-ttl", cfg.ec.AuthTokenTTL, "The lifetime in seconds of the auth token.")
|
||||
|
||||
// gateway
|
||||
fs.BoolVar(&cfg.ec.EnableGRPCGateway, "enable-grpc-gateway", cfg.ec.EnableGRPCGateway, "Enable GRPC gateway.")
|
||||
|
||||
// experimental
|
||||
fs.BoolVar(&cfg.ec.ExperimentalInitialCorruptCheck, "experimental-initial-corrupt-check", cfg.ec.ExperimentalInitialCorruptCheck, "Enable to check data corruption before serving any client/peer traffic.")
|
||||
fs.DurationVar(&cfg.ec.ExperimentalCorruptCheckTime, "experimental-corrupt-check-time", cfg.ec.ExperimentalCorruptCheckTime, "Duration of time between cluster corruption check passes.")
|
||||
fs.StringVar(&cfg.ec.ExperimentalEnableV2V3, "experimental-enable-v2v3", cfg.ec.ExperimentalEnableV2V3, "v3 prefix for serving emulated v2 state.")
|
||||
fs.BoolVar(&cfg.ec.ExperimentalEnableLeaseCheckpoint, "experimental-enable-lease-checkpoint", false, "Enable to persist lease remaining TTL to prevent indefinite auto-renewal of long lived leases.")
|
||||
fs.IntVar(&cfg.ec.ExperimentalCompactionBatchLimit, "experimental-compaction-batch-limit", cfg.ec.ExperimentalCompactionBatchLimit, "Sets the maximum revisions deleted in each compaction batch.")
|
||||
fs.DurationVar(&cfg.ec.ExperimentalWatchProgressNotifyInterval, "experimental-watch-progress-notify-interval", cfg.ec.ExperimentalWatchProgressNotifyInterval, "Duration of periodic watch progress notifications.")
|
||||
fs.DurationVar(&cfg.ec.ExperimentalDowngradeCheckTime, "experimental-downgrade-check-time", cfg.ec.ExperimentalDowngradeCheckTime, "Duration of time between two downgrade status check.")
|
||||
|
||||
// unsafe
|
||||
fs.BoolVar(&cfg.ec.UnsafeNoFsync, "unsafe-no-fsync", false, "Disables fsync, unsafe, will cause data loss.")
|
||||
fs.BoolVar(&cfg.ec.ForceNewCluster, "force-new-cluster", false, "Force to create a new one member cluster.")
|
||||
|
||||
// ignored
|
||||
for _, f := range cfg.ignored {
|
||||
fs.Var(&flags.IgnoredFlag{Name: f}, f, "")
|
||||
}
|
||||
return cfg
|
||||
}
|
||||
|
||||
func (cfg *config) parse(arguments []string) error {
|
||||
perr := cfg.cf.flagSet.Parse(arguments)
|
||||
switch perr {
|
||||
case nil:
|
||||
case flag.ErrHelp:
|
||||
fmt.Println(flagsline)
|
||||
os.Exit(0)
|
||||
default:
|
||||
os.Exit(2)
|
||||
}
|
||||
if len(cfg.cf.flagSet.Args()) != 0 {
|
||||
return fmt.Errorf("'%s' is not a valid flag", cfg.cf.flagSet.Arg(0))
|
||||
}
|
||||
|
||||
if cfg.printVersion {
|
||||
fmt.Printf("etcd Version: %s\n", version.Version)
|
||||
fmt.Printf("Git SHA: %s\n", version.GitSHA)
|
||||
fmt.Printf("Go Version: %s\n", runtime.Version())
|
||||
fmt.Printf("Go OS/Arch: %s/%s\n", runtime.GOOS, runtime.GOARCH)
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
var err error
|
||||
|
||||
// This env variable must be parsed separately
|
||||
// because we need to determine whether to use or
|
||||
// ignore the env variables based on if the config file is set.
|
||||
if cfg.configFile == "" {
|
||||
cfg.configFile = os.Getenv(flags.FlagToEnv("ETCD", "config-file"))
|
||||
}
|
||||
|
||||
if cfg.configFile != "" {
|
||||
err = cfg.configFromFile(cfg.configFile)
|
||||
if lg := cfg.ec.GetLogger(); lg != nil {
|
||||
lg.Info(
|
||||
"loaded server configuration, other configuration command line flags and environment variables will be ignored if provided",
|
||||
zap.String("path", cfg.configFile),
|
||||
)
|
||||
}
|
||||
} else {
|
||||
err = cfg.configFromCmdLine()
|
||||
}
|
||||
// now logger is set up
|
||||
return err
|
||||
}
|
||||
|
||||
func (cfg *config) configFromCmdLine() error {
|
||||
// user-specified logger is not setup yet, use this logger during flag parsing
|
||||
lg, err := zap.NewProduction()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
verKey := "ETCD_VERSION"
|
||||
if verVal := os.Getenv(verKey); verVal != "" {
|
||||
// unset to avoid any possible side-effect.
|
||||
os.Unsetenv(verKey)
|
||||
|
||||
lg.Warn(
|
||||
"cannot set special environment variable",
|
||||
zap.String("key", verKey),
|
||||
zap.String("value", verVal),
|
||||
)
|
||||
}
|
||||
|
||||
err = flags.SetFlagsFromEnv(lg, "ETCD", cfg.cf.flagSet)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cfg.ec.LPUrls = flags.UniqueURLsFromFlag(cfg.cf.flagSet, "listen-peer-urls")
|
||||
cfg.ec.APUrls = flags.UniqueURLsFromFlag(cfg.cf.flagSet, "initial-advertise-peer-urls")
|
||||
cfg.ec.LCUrls = flags.UniqueURLsFromFlag(cfg.cf.flagSet, "listen-client-urls")
|
||||
cfg.ec.ACUrls = flags.UniqueURLsFromFlag(cfg.cf.flagSet, "advertise-client-urls")
|
||||
cfg.ec.ListenMetricsUrls = flags.UniqueURLsFromFlag(cfg.cf.flagSet, "listen-metrics-urls")
|
||||
|
||||
cfg.ec.CORS = flags.UniqueURLsMapFromFlag(cfg.cf.flagSet, "cors")
|
||||
cfg.ec.HostWhitelist = flags.UniqueStringsMapFromFlag(cfg.cf.flagSet, "host-whitelist")
|
||||
|
||||
cfg.ec.CipherSuites = flags.StringsFromFlag(cfg.cf.flagSet, "cipher-suites")
|
||||
|
||||
cfg.ec.LogOutputs = flags.UniqueStringsFromFlag(cfg.cf.flagSet, "log-outputs")
|
||||
|
||||
cfg.ec.ClusterState = cfg.cf.clusterState.String()
|
||||
cfg.cp.Fallback = cfg.cf.fallback.String()
|
||||
cfg.cp.Proxy = cfg.cf.proxy.String()
|
||||
|
||||
// disable default advertise-client-urls if lcurls is set
|
||||
missingAC := flags.IsSet(cfg.cf.flagSet, "listen-client-urls") && !flags.IsSet(cfg.cf.flagSet, "advertise-client-urls")
|
||||
if !cfg.mayBeProxy() && missingAC {
|
||||
cfg.ec.ACUrls = nil
|
||||
}
|
||||
|
||||
// disable default initial-cluster if discovery is set
|
||||
if (cfg.ec.Durl != "" || cfg.ec.DNSCluster != "" || cfg.ec.DNSClusterServiceName != "") && !flags.IsSet(cfg.cf.flagSet, "initial-cluster") {
|
||||
cfg.ec.InitialCluster = ""
|
||||
}
|
||||
|
||||
return cfg.validate()
|
||||
}
|
||||
|
||||
func (cfg *config) configFromFile(path string) error {
|
||||
eCfg, err := embed.ConfigFromFile(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cfg.ec = *eCfg
|
||||
|
||||
// load extra config information
|
||||
b, rerr := ioutil.ReadFile(path)
|
||||
if rerr != nil {
|
||||
return rerr
|
||||
}
|
||||
if yerr := yaml.Unmarshal(b, &cfg.cp); yerr != nil {
|
||||
return yerr
|
||||
}
|
||||
|
||||
if cfg.cp.FallbackJSON != "" {
|
||||
if err := cfg.cf.fallback.Set(cfg.cp.FallbackJSON); err != nil {
|
||||
log.Fatalf("unexpected error setting up discovery-fallback flag: %v", err)
|
||||
}
|
||||
cfg.cp.Fallback = cfg.cf.fallback.String()
|
||||
}
|
||||
|
||||
if cfg.cp.ProxyJSON != "" {
|
||||
if err := cfg.cf.proxy.Set(cfg.cp.ProxyJSON); err != nil {
|
||||
log.Fatalf("unexpected error setting up proxyFlag: %v", err)
|
||||
}
|
||||
cfg.cp.Proxy = cfg.cf.proxy.String()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cfg *config) mayBeProxy() bool {
|
||||
mayFallbackToProxy := cfg.ec.Durl != "" && cfg.cp.Fallback == fallbackFlagProxy
|
||||
return cfg.cp.Proxy != proxyFlagOff || mayFallbackToProxy
|
||||
}
|
||||
|
||||
func (cfg *config) validate() error {
|
||||
err := cfg.ec.Validate()
|
||||
// TODO(yichengq): check this for joining through discovery service case
|
||||
if err == embed.ErrUnsetAdvertiseClientURLsFlag && cfg.mayBeProxy() {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (cfg config) isProxy() bool { return cfg.cf.proxy.String() != proxyFlagOff }
|
||||
func (cfg config) isReadonlyProxy() bool { return cfg.cf.proxy.String() == proxyFlagReadonly }
|
||||
func (cfg config) shouldFallbackToProxy() bool { return cfg.cf.fallback.String() == fallbackFlagProxy }
|
||||
583
server/etcdmain/config_test.go
Normal file
583
server/etcdmain/config_test.go
Normal file
@@ -0,0 +1,583 @@
|
||||
// Copyright 2015 The etcd Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package etcdmain
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/url"
|
||||
"os"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"go.etcd.io/etcd/v3/embed"
|
||||
"sigs.k8s.io/yaml"
|
||||
)
|
||||
|
||||
func TestConfigParsingMemberFlags(t *testing.T) {
|
||||
args := []string{
|
||||
"-data-dir=testdir",
|
||||
"-name=testname",
|
||||
"-max-wals=10",
|
||||
"-max-snapshots=10",
|
||||
"-snapshot-count=10",
|
||||
"-listen-peer-urls=http://localhost:8000,https://localhost:8001",
|
||||
"-listen-client-urls=http://localhost:7000,https://localhost:7001",
|
||||
// it should be set if -listen-client-urls is set
|
||||
"-advertise-client-urls=http://localhost:7000,https://localhost:7001",
|
||||
}
|
||||
|
||||
cfg := newConfig()
|
||||
err := cfg.parse(args)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
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"`
|
||||
SnapshotCount 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",
|
||||
}
|
||||
|
||||
b, err := yaml.Marshal(&yc)
|
||||
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()
|
||||
if err = cfg.parse(args); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
validateMemberFlags(t, cfg)
|
||||
}
|
||||
|
||||
func TestConfigParsingClusteringFlags(t *testing.T) {
|
||||
args := []string{
|
||||
"-initial-cluster=0=http://localhost:8000",
|
||||
"-initial-cluster-state=existing",
|
||||
"-initial-cluster-token=etcdtest",
|
||||
"-initial-advertise-peer-urls=http://localhost:8000,https://localhost:8001",
|
||||
"-advertise-client-urls=http://localhost:7000,https://localhost:7001",
|
||||
"-discovery-fallback=exit",
|
||||
}
|
||||
|
||||
cfg := newConfig()
|
||||
if err := cfg.parse(args); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
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",
|
||||
}
|
||||
|
||||
b, err := yaml.Marshal(&yc)
|
||||
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 != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
validateClusteringFlags(t, cfg)
|
||||
}
|
||||
|
||||
func TestConfigFileClusteringFlags(t *testing.T) {
|
||||
tests := []struct {
|
||||
Name string `json:"name"`
|
||||
InitialCluster string `json:"initial-cluster"`
|
||||
DNSCluster string `json:"discovery-srv"`
|
||||
Durl string `json:"discovery"`
|
||||
}{
|
||||
// Use default name and generate a default initial-cluster
|
||||
{},
|
||||
{
|
||||
Name: "non-default",
|
||||
},
|
||||
{
|
||||
InitialCluster: "0=localhost:8000",
|
||||
},
|
||||
{
|
||||
Name: "non-default",
|
||||
InitialCluster: "0=localhost:8000",
|
||||
},
|
||||
{
|
||||
DNSCluster: "example.com",
|
||||
},
|
||||
{
|
||||
Name: "non-default",
|
||||
DNSCluster: "example.com",
|
||||
},
|
||||
{
|
||||
Durl: "http://example.com/abc",
|
||||
},
|
||||
{
|
||||
Name: "non-default",
|
||||
Durl: "http://example.com/abc",
|
||||
},
|
||||
}
|
||||
|
||||
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()
|
||||
if err := cfg.parse(args); err != nil {
|
||||
t.Errorf("%d: err = %v", i, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestConfigParsingOtherFlags(t *testing.T) {
|
||||
args := []string{"-proxy=readonly"}
|
||||
|
||||
cfg := newConfig()
|
||||
err := cfg.parse(args)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
validateOtherFlags(t, cfg)
|
||||
}
|
||||
|
||||
func TestConfigFileOtherFields(t *testing.T) {
|
||||
yc := struct {
|
||||
ProxyCfgFile string `json:"proxy"`
|
||||
}{
|
||||
"readonly",
|
||||
}
|
||||
|
||||
b, err := yaml.Marshal(&yc)
|
||||
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 != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
validateOtherFlags(t, cfg)
|
||||
}
|
||||
|
||||
func TestConfigParsingConflictClusteringFlags(t *testing.T) {
|
||||
conflictArgs := [][]string{
|
||||
{
|
||||
"-initial-cluster=0=localhost:8000",
|
||||
"-discovery=http://example.com/abc",
|
||||
},
|
||||
{
|
||||
"-discovery-srv=example.com",
|
||||
"-discovery=http://example.com/abc",
|
||||
},
|
||||
{
|
||||
"-initial-cluster=0=localhost:8000",
|
||||
"-discovery-srv=example.com",
|
||||
},
|
||||
{
|
||||
"-initial-cluster=0=localhost:8000",
|
||||
"-discovery=http://example.com/abc",
|
||||
"-discovery-srv=example.com",
|
||||
},
|
||||
}
|
||||
|
||||
for i, tt := range conflictArgs {
|
||||
cfg := newConfig()
|
||||
if err := cfg.parse(tt); err != embed.ErrConflictBootstrapFlags {
|
||||
t.Errorf("%d: err = %v, want %v", i, err, embed.ErrConflictBootstrapFlags)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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()
|
||||
if err := cfg.parse(args); err != embed.ErrConflictBootstrapFlags {
|
||||
t.Errorf("%d: err = %v, want %v", i, err, embed.ErrConflictBootstrapFlags)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestConfigParsingMissedAdvertiseClientURLsFlag(t *testing.T) {
|
||||
tests := []struct {
|
||||
args []string
|
||||
werr error
|
||||
}{
|
||||
{
|
||||
[]string{
|
||||
"-initial-cluster=infra1=http://127.0.0.1:2380",
|
||||
"-listen-client-urls=http://127.0.0.1:2379",
|
||||
},
|
||||
embed.ErrUnsetAdvertiseClientURLsFlag,
|
||||
},
|
||||
{
|
||||
[]string{
|
||||
"-discovery-srv=example.com",
|
||||
"-listen-client-urls=http://127.0.0.1:2379",
|
||||
},
|
||||
embed.ErrUnsetAdvertiseClientURLsFlag,
|
||||
},
|
||||
{
|
||||
[]string{
|
||||
"-discovery=http://example.com/abc",
|
||||
"-discovery-fallback=exit",
|
||||
"-listen-client-urls=http://127.0.0.1:2379",
|
||||
},
|
||||
embed.ErrUnsetAdvertiseClientURLsFlag,
|
||||
},
|
||||
{
|
||||
[]string{
|
||||
"-listen-client-urls=http://127.0.0.1:2379",
|
||||
},
|
||||
embed.ErrUnsetAdvertiseClientURLsFlag,
|
||||
},
|
||||
{
|
||||
[]string{
|
||||
"-discovery=http://example.com/abc",
|
||||
"-listen-client-urls=http://127.0.0.1:2379",
|
||||
},
|
||||
nil,
|
||||
},
|
||||
{
|
||||
[]string{
|
||||
"-proxy=on",
|
||||
"-listen-client-urls=http://127.0.0.1:2379",
|
||||
},
|
||||
nil,
|
||||
},
|
||||
{
|
||||
[]string{
|
||||
"-proxy=readonly",
|
||||
"-listen-client-urls=http://127.0.0.1:2379",
|
||||
},
|
||||
nil,
|
||||
},
|
||||
}
|
||||
|
||||
for i, tt := range tests {
|
||||
cfg := newConfig()
|
||||
if err := cfg.parse(tt.args); err != tt.werr {
|
||||
t.Errorf("%d: err = %v, want %v", i, err, tt.werr)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestConfigIsNewCluster(t *testing.T) {
|
||||
tests := []struct {
|
||||
state string
|
||||
wIsNew bool
|
||||
}{
|
||||
{embed.ClusterStateFlagExisting, false},
|
||||
{embed.ClusterStateFlagNew, true},
|
||||
}
|
||||
for i, tt := range tests {
|
||||
cfg := newConfig()
|
||||
args := []string{"--initial-cluster-state", tests[i].state}
|
||||
if err := cfg.parse(args); err != nil {
|
||||
t.Fatalf("#%d: unexpected clusterState.Set error: %v", i, err)
|
||||
}
|
||||
if g := cfg.ec.IsNewCluster(); g != tt.wIsNew {
|
||||
t.Errorf("#%d: isNewCluster = %v, want %v", i, g, tt.wIsNew)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestConfigIsProxy(t *testing.T) {
|
||||
tests := []struct {
|
||||
proxy string
|
||||
wIsProxy bool
|
||||
}{
|
||||
{proxyFlagOff, false},
|
||||
{proxyFlagReadonly, true},
|
||||
{proxyFlagOn, true},
|
||||
}
|
||||
for i, tt := range tests {
|
||||
cfg := newConfig()
|
||||
if err := cfg.cf.proxy.Set(tt.proxy); err != nil {
|
||||
t.Fatalf("#%d: unexpected proxy.Set error: %v", i, err)
|
||||
}
|
||||
if g := cfg.isProxy(); g != tt.wIsProxy {
|
||||
t.Errorf("#%d: isProxy = %v, want %v", i, g, tt.wIsProxy)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestConfigIsReadonlyProxy(t *testing.T) {
|
||||
tests := []struct {
|
||||
proxy string
|
||||
wIsReadonly bool
|
||||
}{
|
||||
{proxyFlagOff, false},
|
||||
{proxyFlagReadonly, true},
|
||||
{proxyFlagOn, false},
|
||||
}
|
||||
for i, tt := range tests {
|
||||
cfg := newConfig()
|
||||
if err := cfg.cf.proxy.Set(tt.proxy); err != nil {
|
||||
t.Fatalf("#%d: unexpected proxy.Set error: %v", i, err)
|
||||
}
|
||||
if g := cfg.isReadonlyProxy(); g != tt.wIsReadonly {
|
||||
t.Errorf("#%d: isReadonlyProxy = %v, want %v", i, g, tt.wIsReadonly)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestConfigShouldFallbackToProxy(t *testing.T) {
|
||||
tests := []struct {
|
||||
fallback string
|
||||
wFallback bool
|
||||
}{
|
||||
{fallbackFlagProxy, true},
|
||||
{fallbackFlagExit, false},
|
||||
}
|
||||
for i, tt := range tests {
|
||||
cfg := newConfig()
|
||||
if err := cfg.cf.fallback.Set(tt.fallback); err != nil {
|
||||
t.Fatalf("#%d: unexpected fallback.Set error: %v", i, err)
|
||||
}
|
||||
if g := cfg.shouldFallbackToProxy(); g != tt.wFallback {
|
||||
t.Errorf("#%d: shouldFallbackToProxy = %v, want %v", i, g, tt.wFallback)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
TickMs: 10000,
|
||||
errStr: "is too long, and should be set less than",
|
||||
},
|
||||
{
|
||||
ElectionMs: 100,
|
||||
TickMs: 0,
|
||||
errStr: "--heartbeat-interval must be >0 (set to 0ms)",
|
||||
},
|
||||
{
|
||||
ElectionMs: 0,
|
||||
TickMs: 100,
|
||||
errStr: "--election-timeout must be >0 (set to 0ms)",
|
||||
},
|
||||
}
|
||||
|
||||
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()
|
||||
if err := cfg.parse(args); err == nil || !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 := &embed.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",
|
||||
SnapshotCount: 10,
|
||||
}
|
||||
|
||||
if cfg.ec.Dir != wcfg.Dir {
|
||||
t.Errorf("dir = %v, want %v", cfg.ec.Dir, wcfg.Dir)
|
||||
}
|
||||
if cfg.ec.MaxSnapFiles != wcfg.MaxSnapFiles {
|
||||
t.Errorf("maxsnap = %v, want %v", cfg.ec.MaxSnapFiles, wcfg.MaxSnapFiles)
|
||||
}
|
||||
if cfg.ec.MaxWalFiles != wcfg.MaxWalFiles {
|
||||
t.Errorf("maxwal = %v, want %v", cfg.ec.MaxWalFiles, wcfg.MaxWalFiles)
|
||||
}
|
||||
if cfg.ec.Name != wcfg.Name {
|
||||
t.Errorf("name = %v, want %v", cfg.ec.Name, wcfg.Name)
|
||||
}
|
||||
if cfg.ec.SnapshotCount != wcfg.SnapshotCount {
|
||||
t.Errorf("snapcount = %v, want %v", cfg.ec.SnapshotCount, wcfg.SnapshotCount)
|
||||
}
|
||||
if !reflect.DeepEqual(cfg.ec.LPUrls, wcfg.LPUrls) {
|
||||
t.Errorf("listen-peer-urls = %v, want %v", cfg.ec.LPUrls, wcfg.LPUrls)
|
||||
}
|
||||
if !reflect.DeepEqual(cfg.ec.LCUrls, wcfg.LCUrls) {
|
||||
t.Errorf("listen-client-urls = %v, want %v", cfg.ec.LCUrls, wcfg.LCUrls)
|
||||
}
|
||||
}
|
||||
|
||||
func validateClusteringFlags(t *testing.T, cfg *config) {
|
||||
wcfg := newConfig()
|
||||
wcfg.ec.APUrls = []url.URL{{Scheme: "http", Host: "localhost:8000"}, {Scheme: "https", Host: "localhost:8001"}}
|
||||
wcfg.ec.ACUrls = []url.URL{{Scheme: "http", Host: "localhost:7000"}, {Scheme: "https", Host: "localhost:7001"}}
|
||||
wcfg.ec.ClusterState = embed.ClusterStateFlagExisting
|
||||
wcfg.cf.fallback.Set(fallbackFlagExit)
|
||||
wcfg.ec.InitialCluster = "0=http://localhost:8000"
|
||||
wcfg.ec.InitialClusterToken = "etcdtest"
|
||||
|
||||
if cfg.ec.ClusterState != wcfg.ec.ClusterState {
|
||||
t.Errorf("clusterState = %v, want %v", cfg.ec.ClusterState, wcfg.ec.ClusterState)
|
||||
}
|
||||
if cfg.cf.fallback.String() != wcfg.cf.fallback.String() {
|
||||
t.Errorf("fallback = %v, want %v", cfg.cf.fallback, wcfg.cf.fallback)
|
||||
}
|
||||
if cfg.ec.InitialCluster != wcfg.ec.InitialCluster {
|
||||
t.Errorf("initialCluster = %v, want %v", cfg.ec.InitialCluster, wcfg.ec.InitialCluster)
|
||||
}
|
||||
if cfg.ec.InitialClusterToken != wcfg.ec.InitialClusterToken {
|
||||
t.Errorf("initialClusterToken = %v, want %v", cfg.ec.InitialClusterToken, wcfg.ec.InitialClusterToken)
|
||||
}
|
||||
if !reflect.DeepEqual(cfg.ec.APUrls, wcfg.ec.APUrls) {
|
||||
t.Errorf("initial-advertise-peer-urls = %v, want %v", cfg.ec.APUrls, wcfg.ec.APUrls)
|
||||
}
|
||||
if !reflect.DeepEqual(cfg.ec.ACUrls, wcfg.ec.ACUrls) {
|
||||
t.Errorf("advertise-client-urls = %v, want %v", cfg.ec.ACUrls, wcfg.ec.ACUrls)
|
||||
}
|
||||
}
|
||||
|
||||
func validateOtherFlags(t *testing.T, cfg *config) {
|
||||
wcfg := newConfig()
|
||||
wcfg.cf.proxy.Set(proxyFlagReadonly)
|
||||
if cfg.cf.proxy.String() != wcfg.cf.proxy.String() {
|
||||
t.Errorf("proxy = %v, want %v", cfg.cf.proxy, wcfg.cf.proxy)
|
||||
}
|
||||
}
|
||||
16
server/etcdmain/doc.go
Normal file
16
server/etcdmain/doc.go
Normal file
@@ -0,0 +1,16 @@
|
||||
// Copyright 2015 The etcd Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Package etcdmain contains the main entry point for the etcd binary.
|
||||
package etcdmain
|
||||
475
server/etcdmain/etcd.go
Normal file
475
server/etcdmain/etcd.go
Normal file
@@ -0,0 +1,475 @@
|
||||
// Copyright 2015 The etcd Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package etcdmain
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"runtime"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"go.etcd.io/etcd/pkg/v3/fileutil"
|
||||
pkgioutil "go.etcd.io/etcd/pkg/v3/ioutil"
|
||||
"go.etcd.io/etcd/pkg/v3/osutil"
|
||||
"go.etcd.io/etcd/pkg/v3/transport"
|
||||
"go.etcd.io/etcd/pkg/v3/types"
|
||||
"go.etcd.io/etcd/v3/embed"
|
||||
"go.etcd.io/etcd/v3/etcdserver"
|
||||
"go.etcd.io/etcd/v3/etcdserver/api/etcdhttp"
|
||||
"go.etcd.io/etcd/v3/etcdserver/api/v2discovery"
|
||||
"go.etcd.io/etcd/v3/proxy/httpproxy"
|
||||
|
||||
"go.uber.org/zap"
|
||||
"google.golang.org/grpc"
|
||||
)
|
||||
|
||||
type dirType string
|
||||
|
||||
var (
|
||||
dirMember = dirType("member")
|
||||
dirProxy = dirType("proxy")
|
||||
dirEmpty = dirType("empty")
|
||||
)
|
||||
|
||||
func startEtcdOrProxyV2(args []string) {
|
||||
grpc.EnableTracing = false
|
||||
|
||||
cfg := newConfig()
|
||||
defaultInitialCluster := cfg.ec.InitialCluster
|
||||
|
||||
err := cfg.parse(args[1:])
|
||||
lg := cfg.ec.GetLogger()
|
||||
if lg == nil {
|
||||
var zapError error
|
||||
// use this logger
|
||||
lg, zapError = zap.NewProduction()
|
||||
if zapError != nil {
|
||||
fmt.Printf("error creating zap logger %v", zapError)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
lg.Info("Running: ", zap.Strings("args", args))
|
||||
if err != nil {
|
||||
lg.Warn("failed to verify flags", zap.Error(err))
|
||||
switch err {
|
||||
case embed.ErrUnsetAdvertiseClientURLsFlag:
|
||||
lg.Warn("advertise client URLs are not set", zap.Error(err))
|
||||
}
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
defer func() {
|
||||
logger := cfg.ec.GetLogger()
|
||||
if logger != nil {
|
||||
logger.Sync()
|
||||
}
|
||||
}()
|
||||
|
||||
defaultHost, dhErr := (&cfg.ec).UpdateDefaultClusterFromName(defaultInitialCluster)
|
||||
if defaultHost != "" {
|
||||
lg.Info(
|
||||
"detected default host for advertise",
|
||||
zap.String("host", defaultHost),
|
||||
)
|
||||
}
|
||||
if dhErr != nil {
|
||||
lg.Info("failed to detect default host", zap.Error(dhErr))
|
||||
}
|
||||
|
||||
if cfg.ec.Dir == "" {
|
||||
cfg.ec.Dir = fmt.Sprintf("%v.etcd", cfg.ec.Name)
|
||||
lg.Warn(
|
||||
"'data-dir' was empty; using default",
|
||||
zap.String("data-dir", cfg.ec.Dir),
|
||||
)
|
||||
}
|
||||
|
||||
var stopped <-chan struct{}
|
||||
var errc <-chan error
|
||||
|
||||
which := identifyDataDirOrDie(cfg.ec.GetLogger(), cfg.ec.Dir)
|
||||
if which != dirEmpty {
|
||||
lg.Info(
|
||||
"server has been already initialized",
|
||||
zap.String("data-dir", cfg.ec.Dir),
|
||||
zap.String("dir-type", string(which)),
|
||||
)
|
||||
switch which {
|
||||
case dirMember:
|
||||
stopped, errc, err = startEtcd(&cfg.ec)
|
||||
case dirProxy:
|
||||
err = startProxy(cfg)
|
||||
default:
|
||||
lg.Panic(
|
||||
"unknown directory type",
|
||||
zap.String("dir-type", string(which)),
|
||||
)
|
||||
}
|
||||
} else {
|
||||
shouldProxy := cfg.isProxy()
|
||||
if !shouldProxy {
|
||||
stopped, errc, err = startEtcd(&cfg.ec)
|
||||
if derr, ok := err.(*etcdserver.DiscoveryError); ok && derr.Err == v2discovery.ErrFullCluster {
|
||||
if cfg.shouldFallbackToProxy() {
|
||||
lg.Warn(
|
||||
"discovery cluster is full, falling back to proxy",
|
||||
zap.String("fallback-proxy", fallbackFlagProxy),
|
||||
zap.Error(err),
|
||||
)
|
||||
shouldProxy = true
|
||||
}
|
||||
} else if err != nil {
|
||||
lg.Warn("failed to start etcd", zap.Error(err))
|
||||
}
|
||||
}
|
||||
if shouldProxy {
|
||||
err = startProxy(cfg)
|
||||
}
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
if derr, ok := err.(*etcdserver.DiscoveryError); ok {
|
||||
switch derr.Err {
|
||||
case v2discovery.ErrDuplicateID:
|
||||
lg.Warn(
|
||||
"member has been registered with discovery service",
|
||||
zap.String("name", cfg.ec.Name),
|
||||
zap.String("discovery-token", cfg.ec.Durl),
|
||||
zap.Error(derr.Err),
|
||||
)
|
||||
lg.Warn(
|
||||
"but could not find valid cluster configuration",
|
||||
zap.String("data-dir", cfg.ec.Dir),
|
||||
)
|
||||
lg.Warn("check data dir if previous bootstrap succeeded")
|
||||
lg.Warn("or use a new discovery token if previous bootstrap failed")
|
||||
|
||||
case v2discovery.ErrDuplicateName:
|
||||
lg.Warn(
|
||||
"member with duplicated name has already been registered",
|
||||
zap.String("discovery-token", cfg.ec.Durl),
|
||||
zap.Error(derr.Err),
|
||||
)
|
||||
lg.Warn("cURL the discovery token URL for details")
|
||||
lg.Warn("do not reuse discovery token; generate a new one to bootstrap a cluster")
|
||||
|
||||
default:
|
||||
lg.Warn(
|
||||
"failed to bootstrap; discovery token was already used",
|
||||
zap.String("discovery-token", cfg.ec.Durl),
|
||||
zap.Error(err),
|
||||
)
|
||||
lg.Warn("do not reuse discovery token; generate a new one to bootstrap a cluster")
|
||||
}
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if strings.Contains(err.Error(), "include") && strings.Contains(err.Error(), "--initial-cluster") {
|
||||
lg.Warn("failed to start", zap.Error(err))
|
||||
if cfg.ec.InitialCluster == cfg.ec.InitialClusterFromName(cfg.ec.Name) {
|
||||
lg.Warn("forgot to set --initial-cluster?")
|
||||
}
|
||||
if types.URLs(cfg.ec.APUrls).String() == embed.DefaultInitialAdvertisePeerURLs {
|
||||
lg.Warn("forgot to set --initial-advertise-peer-urls?")
|
||||
}
|
||||
if cfg.ec.InitialCluster == cfg.ec.InitialClusterFromName(cfg.ec.Name) && len(cfg.ec.Durl) == 0 {
|
||||
lg.Warn("--discovery flag is not set")
|
||||
}
|
||||
os.Exit(1)
|
||||
}
|
||||
lg.Fatal("discovery failed", zap.Error(err))
|
||||
}
|
||||
|
||||
osutil.HandleInterrupts(lg)
|
||||
|
||||
// At this point, the initialization of etcd is done.
|
||||
// The listeners are listening on the TCP ports and ready
|
||||
// for accepting connections. The etcd instance should be
|
||||
// joined with the cluster and ready to serve incoming
|
||||
// connections.
|
||||
notifySystemd(lg)
|
||||
|
||||
select {
|
||||
case lerr := <-errc:
|
||||
// fatal out on listener errors
|
||||
lg.Fatal("listener failed", zap.Error(lerr))
|
||||
case <-stopped:
|
||||
}
|
||||
|
||||
osutil.Exit(0)
|
||||
}
|
||||
|
||||
// startEtcd runs StartEtcd in addition to hooks needed for standalone etcd.
|
||||
func startEtcd(cfg *embed.Config) (<-chan struct{}, <-chan error, error) {
|
||||
e, err := embed.StartEtcd(cfg)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
osutil.RegisterInterruptHandler(e.Close)
|
||||
select {
|
||||
case <-e.Server.ReadyNotify(): // wait for e.Server to join the cluster
|
||||
case <-e.Server.StopNotify(): // publish aborted from 'ErrStopped'
|
||||
}
|
||||
return e.Server.StopNotify(), e.Err(), nil
|
||||
}
|
||||
|
||||
// startProxy launches an HTTP proxy for client communication which proxies to other etcd nodes.
|
||||
func startProxy(cfg *config) error {
|
||||
lg := cfg.ec.GetLogger()
|
||||
lg.Info("v2 API proxy starting")
|
||||
|
||||
clientTLSInfo := cfg.ec.ClientTLSInfo
|
||||
if clientTLSInfo.Empty() {
|
||||
// Support old proxy behavior of defaulting to PeerTLSInfo
|
||||
// for both client and peer connections.
|
||||
clientTLSInfo = cfg.ec.PeerTLSInfo
|
||||
}
|
||||
clientTLSInfo.InsecureSkipVerify = cfg.ec.ClientAutoTLS
|
||||
cfg.ec.PeerTLSInfo.InsecureSkipVerify = cfg.ec.PeerAutoTLS
|
||||
|
||||
pt, err := transport.NewTimeoutTransport(
|
||||
clientTLSInfo,
|
||||
time.Duration(cfg.cp.ProxyDialTimeoutMs)*time.Millisecond,
|
||||
time.Duration(cfg.cp.ProxyReadTimeoutMs)*time.Millisecond,
|
||||
time.Duration(cfg.cp.ProxyWriteTimeoutMs)*time.Millisecond,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
pt.MaxIdleConnsPerHost = httpproxy.DefaultMaxIdleConnsPerHost
|
||||
|
||||
if err = cfg.ec.PeerSelfCert(); err != nil {
|
||||
lg.Fatal("failed to get self-signed certs for peer", zap.Error(err))
|
||||
}
|
||||
tr, err := transport.NewTimeoutTransport(
|
||||
cfg.ec.PeerTLSInfo,
|
||||
time.Duration(cfg.cp.ProxyDialTimeoutMs)*time.Millisecond,
|
||||
time.Duration(cfg.cp.ProxyReadTimeoutMs)*time.Millisecond,
|
||||
time.Duration(cfg.cp.ProxyWriteTimeoutMs)*time.Millisecond,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cfg.ec.Dir = filepath.Join(cfg.ec.Dir, "proxy")
|
||||
err = fileutil.TouchDirAll(cfg.ec.Dir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var peerURLs []string
|
||||
clusterfile := filepath.Join(cfg.ec.Dir, "cluster")
|
||||
|
||||
b, err := ioutil.ReadFile(clusterfile)
|
||||
switch {
|
||||
case err == nil:
|
||||
if cfg.ec.Durl != "" {
|
||||
lg.Warn(
|
||||
"discovery token ignored since the proxy has already been initialized; valid cluster file found",
|
||||
zap.String("cluster-file", clusterfile),
|
||||
)
|
||||
}
|
||||
if cfg.ec.DNSCluster != "" {
|
||||
lg.Warn(
|
||||
"DNS SRV discovery ignored since the proxy has already been initialized; valid cluster file found",
|
||||
zap.String("cluster-file", clusterfile),
|
||||
)
|
||||
}
|
||||
urls := struct{ PeerURLs []string }{}
|
||||
err = json.Unmarshal(b, &urls)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
peerURLs = urls.PeerURLs
|
||||
lg.Info(
|
||||
"proxy using peer URLS from cluster file",
|
||||
zap.Strings("peer-urls", peerURLs),
|
||||
zap.String("cluster-file", clusterfile),
|
||||
)
|
||||
|
||||
case os.IsNotExist(err):
|
||||
var urlsmap types.URLsMap
|
||||
urlsmap, _, err = cfg.ec.PeerURLsMapAndToken("proxy")
|
||||
if err != nil {
|
||||
return fmt.Errorf("error setting up initial cluster: %v", err)
|
||||
}
|
||||
|
||||
if cfg.ec.Durl != "" {
|
||||
var s string
|
||||
s, err = v2discovery.GetCluster(lg, cfg.ec.Durl, cfg.ec.Dproxy)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if urlsmap, err = types.NewURLsMap(s); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
peerURLs = urlsmap.URLs()
|
||||
lg.Info("proxy using peer URLS", zap.Strings("peer-urls", peerURLs))
|
||||
|
||||
default:
|
||||
return err
|
||||
}
|
||||
|
||||
clientURLs := []string{}
|
||||
uf := func() []string {
|
||||
gcls, gerr := etcdserver.GetClusterFromRemotePeers(lg, peerURLs, tr)
|
||||
if gerr != nil {
|
||||
lg.Warn(
|
||||
"failed to get cluster from remote peers",
|
||||
zap.Strings("peer-urls", peerURLs),
|
||||
zap.Error(gerr),
|
||||
)
|
||||
return []string{}
|
||||
}
|
||||
|
||||
clientURLs = gcls.ClientURLs()
|
||||
urls := struct{ PeerURLs []string }{gcls.PeerURLs()}
|
||||
b, jerr := json.Marshal(urls)
|
||||
if jerr != nil {
|
||||
lg.Warn("proxy failed to marshal peer URLs", zap.Error(jerr))
|
||||
return clientURLs
|
||||
}
|
||||
|
||||
err = pkgioutil.WriteAndSyncFile(clusterfile+".bak", b, 0600)
|
||||
if err != nil {
|
||||
lg.Warn("proxy failed to write cluster file", zap.Error(err))
|
||||
return clientURLs
|
||||
}
|
||||
err = os.Rename(clusterfile+".bak", clusterfile)
|
||||
if err != nil {
|
||||
lg.Warn(
|
||||
"proxy failed to rename cluster file",
|
||||
zap.String("path", clusterfile),
|
||||
zap.Error(err),
|
||||
)
|
||||
return clientURLs
|
||||
}
|
||||
if !reflect.DeepEqual(gcls.PeerURLs(), peerURLs) {
|
||||
lg.Info(
|
||||
"proxy updated peer URLs",
|
||||
zap.Strings("from", peerURLs),
|
||||
zap.Strings("to", gcls.PeerURLs()),
|
||||
)
|
||||
}
|
||||
peerURLs = gcls.PeerURLs()
|
||||
|
||||
return clientURLs
|
||||
}
|
||||
ph := httpproxy.NewHandler(lg, pt, uf, time.Duration(cfg.cp.ProxyFailureWaitMs)*time.Millisecond, time.Duration(cfg.cp.ProxyRefreshIntervalMs)*time.Millisecond)
|
||||
ph = embed.WrapCORS(cfg.ec.CORS, ph)
|
||||
|
||||
if cfg.isReadonlyProxy() {
|
||||
ph = httpproxy.NewReadonlyHandler(ph)
|
||||
}
|
||||
|
||||
// setup self signed certs when serving https
|
||||
cHosts, cTLS := []string{}, false
|
||||
for _, u := range cfg.ec.LCUrls {
|
||||
cHosts = append(cHosts, u.Host)
|
||||
cTLS = cTLS || u.Scheme == "https"
|
||||
}
|
||||
for _, u := range cfg.ec.ACUrls {
|
||||
cHosts = append(cHosts, u.Host)
|
||||
cTLS = cTLS || u.Scheme == "https"
|
||||
}
|
||||
listenerTLS := cfg.ec.ClientTLSInfo
|
||||
if cfg.ec.ClientAutoTLS && cTLS {
|
||||
listenerTLS, err = transport.SelfCert(cfg.ec.GetLogger(), filepath.Join(cfg.ec.Dir, "clientCerts"), cHosts)
|
||||
if err != nil {
|
||||
lg.Fatal("failed to initialize self-signed client cert", zap.Error(err))
|
||||
}
|
||||
}
|
||||
|
||||
// Start a proxy server goroutine for each listen address
|
||||
for _, u := range cfg.ec.LCUrls {
|
||||
l, err := transport.NewListener(u.Host, u.Scheme, &listenerTLS)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
host := u.String()
|
||||
go func() {
|
||||
lg.Info("v2 proxy started listening on client requests", zap.String("host", host))
|
||||
mux := http.NewServeMux()
|
||||
etcdhttp.HandlePrometheus(mux) // v2 proxy just uses the same port
|
||||
mux.Handle("/", ph)
|
||||
lg.Fatal("done serving", zap.Error(http.Serve(l, mux)))
|
||||
}()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// identifyDataDirOrDie returns the type of the data dir.
|
||||
// Dies if the datadir is invalid.
|
||||
func identifyDataDirOrDie(lg *zap.Logger, dir string) dirType {
|
||||
names, err := fileutil.ReadDir(dir)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return dirEmpty
|
||||
}
|
||||
lg.Fatal("failed to list data directory", zap.String("dir", dir), zap.Error(err))
|
||||
}
|
||||
|
||||
var m, p bool
|
||||
for _, name := range names {
|
||||
switch dirType(name) {
|
||||
case dirMember:
|
||||
m = true
|
||||
case dirProxy:
|
||||
p = true
|
||||
default:
|
||||
lg.Warn(
|
||||
"found invalid file under data directory",
|
||||
zap.String("filename", name),
|
||||
zap.String("data-dir", dir),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if m && p {
|
||||
lg.Fatal("invalid datadir; both member and proxy directories exist")
|
||||
}
|
||||
if m {
|
||||
return dirMember
|
||||
}
|
||||
if p {
|
||||
return dirProxy
|
||||
}
|
||||
return dirEmpty
|
||||
}
|
||||
|
||||
func checkSupportArch() {
|
||||
// TODO qualify arm64
|
||||
if runtime.GOARCH == "amd64" || runtime.GOARCH == "ppc64le" || runtime.GOARCH == "s390x" {
|
||||
return
|
||||
}
|
||||
// unsupported arch only configured via environment variable
|
||||
// so unset here to not parse through flag
|
||||
defer os.Unsetenv("ETCD_UNSUPPORTED_ARCH")
|
||||
if env, ok := os.LookupEnv("ETCD_UNSUPPORTED_ARCH"); ok && env == runtime.GOARCH {
|
||||
fmt.Printf("running etcd on unsupported architecture %q since ETCD_UNSUPPORTED_ARCH is set\n", env)
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Printf("etcd on unsupported platform without ETCD_UNSUPPORTED_ARCH=%s set\n", runtime.GOARCH)
|
||||
os.Exit(1)
|
||||
}
|
||||
183
server/etcdmain/gateway.go
Normal file
183
server/etcdmain/gateway.go
Normal file
@@ -0,0 +1,183 @@
|
||||
// Copyright 2016 The etcd Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package etcdmain
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"net/url"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"go.etcd.io/etcd/v3/proxy/tcpproxy"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
var (
|
||||
gatewayListenAddr string
|
||||
gatewayEndpoints []string
|
||||
gatewayDNSCluster string
|
||||
gatewayDNSClusterServiceName string
|
||||
gatewayInsecureDiscovery bool
|
||||
gatewayRetryDelay time.Duration
|
||||
gatewayCA string
|
||||
)
|
||||
|
||||
var (
|
||||
rootCmd = &cobra.Command{
|
||||
Use: "etcd",
|
||||
Short: "etcd server",
|
||||
SuggestFor: []string{"etcd"},
|
||||
}
|
||||
)
|
||||
|
||||
func init() {
|
||||
rootCmd.AddCommand(newGatewayCommand())
|
||||
}
|
||||
|
||||
// newGatewayCommand returns the cobra command for "gateway".
|
||||
func newGatewayCommand() *cobra.Command {
|
||||
lpc := &cobra.Command{
|
||||
Use: "gateway <subcommand>",
|
||||
Short: "gateway related command",
|
||||
}
|
||||
lpc.AddCommand(newGatewayStartCommand())
|
||||
|
||||
return lpc
|
||||
}
|
||||
|
||||
func newGatewayStartCommand() *cobra.Command {
|
||||
cmd := cobra.Command{
|
||||
Use: "start",
|
||||
Short: "start the gateway",
|
||||
Run: startGateway,
|
||||
}
|
||||
|
||||
cmd.Flags().StringVar(&gatewayListenAddr, "listen-addr", "127.0.0.1:23790", "listen address")
|
||||
cmd.Flags().StringVar(&gatewayDNSCluster, "discovery-srv", "", "DNS domain used to bootstrap initial cluster")
|
||||
cmd.Flags().StringVar(&gatewayDNSClusterServiceName, "discovery-srv-name", "", "service name to query when using DNS discovery")
|
||||
cmd.Flags().BoolVar(&gatewayInsecureDiscovery, "insecure-discovery", false, "accept insecure SRV records")
|
||||
cmd.Flags().StringVar(&gatewayCA, "trusted-ca-file", "", "path to the client server TLS CA file for verifying the discovered endpoints when discovery-srv is provided.")
|
||||
|
||||
cmd.Flags().StringSliceVar(&gatewayEndpoints, "endpoints", []string{"127.0.0.1:2379"}, "comma separated etcd cluster endpoints")
|
||||
|
||||
cmd.Flags().DurationVar(&gatewayRetryDelay, "retry-delay", time.Minute, "duration of delay before retrying failed endpoints")
|
||||
|
||||
return &cmd
|
||||
}
|
||||
|
||||
func stripSchema(eps []string) []string {
|
||||
var endpoints []string
|
||||
for _, ep := range eps {
|
||||
if u, err := url.Parse(ep); err == nil && u.Host != "" {
|
||||
ep = u.Host
|
||||
}
|
||||
endpoints = append(endpoints, ep)
|
||||
}
|
||||
return endpoints
|
||||
}
|
||||
|
||||
func startGateway(cmd *cobra.Command, args []string) {
|
||||
var lg *zap.Logger
|
||||
lg, err := zap.NewProduction()
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// We use os.Args to show all the arguments (not only passed-through Cobra).
|
||||
lg.Info("Running: ", zap.Strings("args", os.Args))
|
||||
|
||||
srvs := discoverEndpoints(lg, gatewayDNSCluster, gatewayCA, gatewayInsecureDiscovery, gatewayDNSClusterServiceName)
|
||||
if len(srvs.Endpoints) == 0 {
|
||||
// no endpoints discovered, fall back to provided endpoints
|
||||
srvs.Endpoints = gatewayEndpoints
|
||||
}
|
||||
// Strip the schema from the endpoints because we start just a TCP proxy
|
||||
srvs.Endpoints = stripSchema(srvs.Endpoints)
|
||||
if len(srvs.SRVs) == 0 {
|
||||
for _, ep := range srvs.Endpoints {
|
||||
h, p, serr := net.SplitHostPort(ep)
|
||||
if serr != nil {
|
||||
fmt.Printf("error parsing endpoint %q", ep)
|
||||
os.Exit(1)
|
||||
}
|
||||
var port uint16
|
||||
fmt.Sscanf(p, "%d", &port)
|
||||
srvs.SRVs = append(srvs.SRVs, &net.SRV{Target: h, Port: port})
|
||||
}
|
||||
}
|
||||
|
||||
lhost, lport, err := net.SplitHostPort(gatewayListenAddr)
|
||||
if err != nil {
|
||||
fmt.Println("failed to validate listen address:", gatewayListenAddr)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
laddrs, err := net.LookupHost(lhost)
|
||||
if err != nil {
|
||||
fmt.Println("failed to resolve listen host:", lhost)
|
||||
os.Exit(1)
|
||||
}
|
||||
laddrsMap := make(map[string]bool)
|
||||
for _, addr := range laddrs {
|
||||
laddrsMap[addr] = true
|
||||
}
|
||||
|
||||
for _, srv := range srvs.SRVs {
|
||||
var eaddrs []string
|
||||
eaddrs, err = net.LookupHost(srv.Target)
|
||||
if err != nil {
|
||||
fmt.Println("failed to resolve endpoint host:", srv.Target)
|
||||
os.Exit(1)
|
||||
}
|
||||
if fmt.Sprintf("%d", srv.Port) != lport {
|
||||
continue
|
||||
}
|
||||
|
||||
for _, ea := range eaddrs {
|
||||
if laddrsMap[ea] {
|
||||
fmt.Printf("SRV or endpoint (%s:%d->%s:%d) should not resolve to the gateway listen addr (%s)\n", srv.Target, srv.Port, ea, srv.Port, gatewayListenAddr)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(srvs.Endpoints) == 0 {
|
||||
fmt.Println("no endpoints found")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
var l net.Listener
|
||||
l, err = net.Listen("tcp", gatewayListenAddr)
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
tp := tcpproxy.TCPProxy{
|
||||
Logger: lg,
|
||||
Listener: l,
|
||||
Endpoints: srvs.SRVs,
|
||||
MonitorInterval: gatewayRetryDelay,
|
||||
}
|
||||
|
||||
// At this point, etcd gateway listener is initialized
|
||||
notifySystemd(lg)
|
||||
|
||||
tp.Run()
|
||||
}
|
||||
527
server/etcdmain/grpc_proxy.go
Normal file
527
server/etcdmain/grpc_proxy.go
Normal file
@@ -0,0 +1,527 @@
|
||||
// Copyright 2016 The etcd Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package etcdmain
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"math"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
pb "go.etcd.io/etcd/api/v3/etcdserverpb"
|
||||
"go.etcd.io/etcd/client/v3"
|
||||
"go.etcd.io/etcd/client/v3/leasing"
|
||||
"go.etcd.io/etcd/client/v3/namespace"
|
||||
"go.etcd.io/etcd/client/v3/ordering"
|
||||
"go.etcd.io/etcd/pkg/v3/debugutil"
|
||||
"go.etcd.io/etcd/pkg/v3/logutil"
|
||||
"go.etcd.io/etcd/pkg/v3/transport"
|
||||
"go.etcd.io/etcd/v3/embed"
|
||||
"go.etcd.io/etcd/v3/etcdserver/api/v3election/v3electionpb"
|
||||
"go.etcd.io/etcd/v3/etcdserver/api/v3lock/v3lockpb"
|
||||
"go.etcd.io/etcd/v3/proxy/grpcproxy"
|
||||
|
||||
grpc_prometheus "github.com/grpc-ecosystem/go-grpc-prometheus"
|
||||
"github.com/soheilhy/cmux"
|
||||
"github.com/spf13/cobra"
|
||||
"go.uber.org/zap"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/grpclog"
|
||||
"google.golang.org/grpc/keepalive"
|
||||
)
|
||||
|
||||
var (
|
||||
grpcProxyListenAddr string
|
||||
grpcProxyMetricsListenAddr string
|
||||
grpcProxyEndpoints []string
|
||||
grpcProxyDNSCluster string
|
||||
grpcProxyDNSClusterServiceName string
|
||||
grpcProxyInsecureDiscovery bool
|
||||
grpcProxyDataDir string
|
||||
grpcMaxCallSendMsgSize int
|
||||
grpcMaxCallRecvMsgSize int
|
||||
|
||||
// tls for connecting to etcd
|
||||
|
||||
grpcProxyCA string
|
||||
grpcProxyCert string
|
||||
grpcProxyKey string
|
||||
grpcProxyInsecureSkipTLSVerify bool
|
||||
|
||||
// tls for clients connecting to proxy
|
||||
|
||||
grpcProxyListenCA string
|
||||
grpcProxyListenCert string
|
||||
grpcProxyListenKey string
|
||||
grpcProxyListenAutoTLS bool
|
||||
grpcProxyListenCRL string
|
||||
|
||||
grpcProxyAdvertiseClientURL string
|
||||
grpcProxyResolverPrefix string
|
||||
grpcProxyResolverTTL int
|
||||
|
||||
grpcProxyNamespace string
|
||||
grpcProxyLeasing string
|
||||
|
||||
grpcProxyEnablePprof bool
|
||||
grpcProxyEnableOrdering bool
|
||||
|
||||
grpcProxyDebug bool
|
||||
|
||||
// GRPC keep alive related options.
|
||||
grpcKeepAliveMinTime time.Duration
|
||||
grpcKeepAliveTimeout time.Duration
|
||||
grpcKeepAliveInterval time.Duration
|
||||
)
|
||||
|
||||
const defaultGRPCMaxCallSendMsgSize = 1.5 * 1024 * 1024
|
||||
|
||||
func init() {
|
||||
rootCmd.AddCommand(newGRPCProxyCommand())
|
||||
}
|
||||
|
||||
// newGRPCProxyCommand returns the cobra command for "grpc-proxy".
|
||||
func newGRPCProxyCommand() *cobra.Command {
|
||||
lpc := &cobra.Command{
|
||||
Use: "grpc-proxy <subcommand>",
|
||||
Short: "grpc-proxy related command",
|
||||
}
|
||||
lpc.AddCommand(newGRPCProxyStartCommand())
|
||||
|
||||
return lpc
|
||||
}
|
||||
|
||||
func newGRPCProxyStartCommand() *cobra.Command {
|
||||
cmd := cobra.Command{
|
||||
Use: "start",
|
||||
Short: "start the grpc proxy",
|
||||
Run: startGRPCProxy,
|
||||
}
|
||||
|
||||
cmd.Flags().StringVar(&grpcProxyListenAddr, "listen-addr", "127.0.0.1:23790", "listen address")
|
||||
cmd.Flags().StringVar(&grpcProxyDNSCluster, "discovery-srv", "", "domain name to query for SRV records describing cluster endpoints")
|
||||
cmd.Flags().StringVar(&grpcProxyDNSClusterServiceName, "discovery-srv-name", "", "service name to query when using DNS discovery")
|
||||
cmd.Flags().StringVar(&grpcProxyMetricsListenAddr, "metrics-addr", "", "listen for endpoint /metrics requests on an additional interface")
|
||||
cmd.Flags().BoolVar(&grpcProxyInsecureDiscovery, "insecure-discovery", false, "accept insecure SRV records")
|
||||
cmd.Flags().StringSliceVar(&grpcProxyEndpoints, "endpoints", []string{"127.0.0.1:2379"}, "comma separated etcd cluster endpoints")
|
||||
cmd.Flags().StringVar(&grpcProxyAdvertiseClientURL, "advertise-client-url", "127.0.0.1:23790", "advertise address to register (must be reachable by client)")
|
||||
cmd.Flags().StringVar(&grpcProxyResolverPrefix, "resolver-prefix", "", "prefix to use for registering proxy (must be shared with other grpc-proxy members)")
|
||||
cmd.Flags().IntVar(&grpcProxyResolverTTL, "resolver-ttl", 0, "specify TTL, in seconds, when registering proxy endpoints")
|
||||
cmd.Flags().StringVar(&grpcProxyNamespace, "namespace", "", "string to prefix to all keys for namespacing requests")
|
||||
cmd.Flags().BoolVar(&grpcProxyEnablePprof, "enable-pprof", false, `Enable runtime profiling data via HTTP server. Address is at client URL + "/debug/pprof/"`)
|
||||
cmd.Flags().StringVar(&grpcProxyDataDir, "data-dir", "default.proxy", "Data directory for persistent data")
|
||||
cmd.Flags().IntVar(&grpcMaxCallSendMsgSize, "max-send-bytes", defaultGRPCMaxCallSendMsgSize, "message send limits in bytes (default value is 1.5 MiB)")
|
||||
cmd.Flags().IntVar(&grpcMaxCallRecvMsgSize, "max-recv-bytes", math.MaxInt32, "message receive limits in bytes (default value is math.MaxInt32)")
|
||||
cmd.Flags().DurationVar(&grpcKeepAliveMinTime, "grpc-keepalive-min-time", embed.DefaultGRPCKeepAliveMinTime, "Minimum interval duration that a client should wait before pinging proxy.")
|
||||
cmd.Flags().DurationVar(&grpcKeepAliveInterval, "grpc-keepalive-interval", embed.DefaultGRPCKeepAliveInterval, "Frequency duration of server-to-client ping to check if a connection is alive (0 to disable).")
|
||||
cmd.Flags().DurationVar(&grpcKeepAliveTimeout, "grpc-keepalive-timeout", embed.DefaultGRPCKeepAliveTimeout, "Additional duration of wait before closing a non-responsive connection (0 to disable).")
|
||||
|
||||
// client TLS for connecting to server
|
||||
cmd.Flags().StringVar(&grpcProxyCert, "cert", "", "identify secure connections with etcd servers using this TLS certificate file")
|
||||
cmd.Flags().StringVar(&grpcProxyKey, "key", "", "identify secure connections with etcd servers using this TLS key file")
|
||||
cmd.Flags().StringVar(&grpcProxyCA, "cacert", "", "verify certificates of TLS-enabled secure etcd servers using this CA bundle")
|
||||
cmd.Flags().BoolVar(&grpcProxyInsecureSkipTLSVerify, "insecure-skip-tls-verify", false, "skip authentication of etcd server TLS certificates (CAUTION: this option should be enabled only for testing purposes)")
|
||||
|
||||
// client TLS for connecting to proxy
|
||||
cmd.Flags().StringVar(&grpcProxyListenCert, "cert-file", "", "identify secure connections to the proxy using this TLS certificate file")
|
||||
cmd.Flags().StringVar(&grpcProxyListenKey, "key-file", "", "identify secure connections to the proxy using this TLS key file")
|
||||
cmd.Flags().StringVar(&grpcProxyListenCA, "trusted-ca-file", "", "verify certificates of TLS-enabled secure proxy using this CA bundle")
|
||||
cmd.Flags().BoolVar(&grpcProxyListenAutoTLS, "auto-tls", false, "proxy TLS using generated certificates")
|
||||
cmd.Flags().StringVar(&grpcProxyListenCRL, "client-crl-file", "", "proxy client certificate revocation list file.")
|
||||
|
||||
// experimental flags
|
||||
cmd.Flags().BoolVar(&grpcProxyEnableOrdering, "experimental-serializable-ordering", false, "Ensure serializable reads have monotonically increasing store revisions across endpoints.")
|
||||
cmd.Flags().StringVar(&grpcProxyLeasing, "experimental-leasing-prefix", "", "leasing metadata prefix for disconnected linearized reads.")
|
||||
|
||||
cmd.Flags().BoolVar(&grpcProxyDebug, "debug", false, "Enable debug-level logging for grpc-proxy.")
|
||||
|
||||
return &cmd
|
||||
}
|
||||
|
||||
func startGRPCProxy(cmd *cobra.Command, args []string) {
|
||||
checkArgs()
|
||||
|
||||
lcfg := logutil.DefaultZapLoggerConfig
|
||||
if grpcProxyDebug {
|
||||
lcfg.Level = zap.NewAtomicLevelAt(zap.DebugLevel)
|
||||
grpc.EnableTracing = true
|
||||
}
|
||||
|
||||
lg, err := lcfg.Build()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer lg.Sync()
|
||||
|
||||
var gl grpclog.LoggerV2
|
||||
gl, err = logutil.NewGRPCLoggerV2(lcfg)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
grpclog.SetLoggerV2(gl)
|
||||
|
||||
// The proxy itself (ListenCert) can have not-empty CN.
|
||||
// The empty CN is required for grpcProxyCert.
|
||||
// Please see https://github.com/etcd-io/etcd/issues/11970#issuecomment-687875315 for more context.
|
||||
tlsinfo := newTLS(grpcProxyListenCA, grpcProxyListenCert, grpcProxyListenKey, false)
|
||||
|
||||
if tlsinfo == nil && grpcProxyListenAutoTLS {
|
||||
host := []string{"https://" + grpcProxyListenAddr}
|
||||
dir := filepath.Join(grpcProxyDataDir, "fixtures", "proxy")
|
||||
autoTLS, err := transport.SelfCert(lg, dir, host)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
tlsinfo = &autoTLS
|
||||
}
|
||||
if tlsinfo != nil {
|
||||
lg.Info("gRPC proxy server TLS", zap.String("tls-info", fmt.Sprintf("%+v", tlsinfo)))
|
||||
}
|
||||
m := mustListenCMux(lg, tlsinfo)
|
||||
grpcl := m.Match(cmux.HTTP2())
|
||||
defer func() {
|
||||
grpcl.Close()
|
||||
lg.Info("stop listening gRPC proxy client requests", zap.String("address", grpcProxyListenAddr))
|
||||
}()
|
||||
|
||||
client := mustNewClient(lg)
|
||||
proxyClient := mustNewProxyClient(lg, tlsinfo)
|
||||
httpClient := mustNewHTTPClient(lg)
|
||||
|
||||
srvhttp, httpl := mustHTTPListener(lg, m, tlsinfo, client, proxyClient)
|
||||
errc := make(chan error)
|
||||
go func() { errc <- newGRPCProxyServer(lg, client).Serve(grpcl) }()
|
||||
go func() { errc <- srvhttp.Serve(httpl) }()
|
||||
go func() { errc <- m.Serve() }()
|
||||
if len(grpcProxyMetricsListenAddr) > 0 {
|
||||
mhttpl := mustMetricsListener(lg, tlsinfo)
|
||||
go func() {
|
||||
mux := http.NewServeMux()
|
||||
grpcproxy.HandleMetrics(mux, httpClient, client.Endpoints())
|
||||
grpcproxy.HandleHealth(lg, mux, client)
|
||||
grpcproxy.HandleProxyMetrics(mux)
|
||||
grpcproxy.HandleProxyHealth(lg, mux, proxyClient)
|
||||
lg.Info("gRPC proxy server metrics URL serving")
|
||||
herr := http.Serve(mhttpl, mux)
|
||||
if herr != nil {
|
||||
lg.Fatal("gRPC proxy server metrics URL returned", zap.Error(herr))
|
||||
} else {
|
||||
lg.Info("gRPC proxy server metrics URL returned")
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
lg.Info("started gRPC proxy", zap.String("address", grpcProxyListenAddr))
|
||||
|
||||
// grpc-proxy is initialized, ready to serve
|
||||
notifySystemd(lg)
|
||||
|
||||
fmt.Fprintln(os.Stderr, <-errc)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
func checkArgs() {
|
||||
if grpcProxyResolverPrefix != "" && grpcProxyResolverTTL < 1 {
|
||||
fmt.Fprintln(os.Stderr, fmt.Errorf("invalid resolver-ttl %d", grpcProxyResolverTTL))
|
||||
os.Exit(1)
|
||||
}
|
||||
if grpcProxyResolverPrefix == "" && grpcProxyResolverTTL > 0 {
|
||||
fmt.Fprintln(os.Stderr, fmt.Errorf("invalid resolver-prefix %q", grpcProxyResolverPrefix))
|
||||
os.Exit(1)
|
||||
}
|
||||
if grpcProxyResolverPrefix != "" && grpcProxyResolverTTL > 0 && grpcProxyAdvertiseClientURL == "" {
|
||||
fmt.Fprintln(os.Stderr, fmt.Errorf("invalid advertise-client-url %q", grpcProxyAdvertiseClientURL))
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
func mustNewClient(lg *zap.Logger) *clientv3.Client {
|
||||
srvs := discoverEndpoints(lg, grpcProxyDNSCluster, grpcProxyCA, grpcProxyInsecureDiscovery, grpcProxyDNSClusterServiceName)
|
||||
eps := srvs.Endpoints
|
||||
if len(eps) == 0 {
|
||||
eps = grpcProxyEndpoints
|
||||
}
|
||||
cfg, err := newClientCfg(lg, eps)
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
cfg.DialOptions = append(cfg.DialOptions,
|
||||
grpc.WithUnaryInterceptor(grpcproxy.AuthUnaryClientInterceptor))
|
||||
cfg.DialOptions = append(cfg.DialOptions,
|
||||
grpc.WithStreamInterceptor(grpcproxy.AuthStreamClientInterceptor))
|
||||
client, err := clientv3.New(*cfg)
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
return client
|
||||
}
|
||||
|
||||
func mustNewProxyClient(lg *zap.Logger, tls *transport.TLSInfo) *clientv3.Client {
|
||||
eps := []string{grpcProxyAdvertiseClientURL}
|
||||
cfg, err := newProxyClientCfg(lg, eps, tls)
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
client, err := clientv3.New(*cfg)
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
lg.Info("create proxy client", zap.String("grpcProxyAdvertiseClientURL", grpcProxyAdvertiseClientURL))
|
||||
return client
|
||||
}
|
||||
|
||||
func newProxyClientCfg(lg *zap.Logger, eps []string, tls *transport.TLSInfo) (*clientv3.Config, error) {
|
||||
cfg := clientv3.Config{
|
||||
Endpoints: eps,
|
||||
DialTimeout: 5 * time.Second,
|
||||
}
|
||||
if tls != nil {
|
||||
clientTLS, err := tls.ClientConfig()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cfg.TLS = clientTLS
|
||||
}
|
||||
return &cfg, nil
|
||||
}
|
||||
|
||||
func newClientCfg(lg *zap.Logger, eps []string) (*clientv3.Config, error) {
|
||||
// set tls if any one tls option set
|
||||
cfg := clientv3.Config{
|
||||
Endpoints: eps,
|
||||
DialTimeout: 5 * time.Second,
|
||||
}
|
||||
|
||||
if grpcMaxCallSendMsgSize > 0 {
|
||||
cfg.MaxCallSendMsgSize = grpcMaxCallSendMsgSize
|
||||
}
|
||||
if grpcMaxCallRecvMsgSize > 0 {
|
||||
cfg.MaxCallRecvMsgSize = grpcMaxCallRecvMsgSize
|
||||
}
|
||||
|
||||
tls := newTLS(grpcProxyCA, grpcProxyCert, grpcProxyKey, true)
|
||||
if tls == nil && grpcProxyInsecureSkipTLSVerify {
|
||||
tls = &transport.TLSInfo{}
|
||||
}
|
||||
if tls != nil {
|
||||
clientTLS, err := tls.ClientConfig()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
clientTLS.InsecureSkipVerify = grpcProxyInsecureSkipTLSVerify
|
||||
if clientTLS.InsecureSkipVerify {
|
||||
lg.Warn("--insecure-skip-tls-verify was given, this grpc proxy process skips authentication of etcd server TLS certificates. This option should be enabled only for testing purposes.")
|
||||
}
|
||||
cfg.TLS = clientTLS
|
||||
lg.Info("gRPC proxy client TLS", zap.String("tls-info", fmt.Sprintf("%+v", tls)))
|
||||
}
|
||||
return &cfg, nil
|
||||
}
|
||||
|
||||
func newTLS(ca, cert, key string, requireEmptyCN bool) *transport.TLSInfo {
|
||||
if ca == "" && cert == "" && key == "" {
|
||||
return nil
|
||||
}
|
||||
return &transport.TLSInfo{TrustedCAFile: ca, CertFile: cert, KeyFile: key, EmptyCN: requireEmptyCN}
|
||||
}
|
||||
|
||||
func mustListenCMux(lg *zap.Logger, tlsinfo *transport.TLSInfo) cmux.CMux {
|
||||
l, err := net.Listen("tcp", grpcProxyListenAddr)
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if l, err = transport.NewKeepAliveListener(l, "tcp", nil); err != nil {
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
if tlsinfo != nil {
|
||||
tlsinfo.CRLFile = grpcProxyListenCRL
|
||||
if l, err = transport.NewTLSListener(l, tlsinfo); err != nil {
|
||||
lg.Fatal("failed to create TLS listener", zap.Error(err))
|
||||
}
|
||||
}
|
||||
|
||||
lg.Info("listening for gRPC proxy client requests", zap.String("address", grpcProxyListenAddr))
|
||||
return cmux.New(l)
|
||||
}
|
||||
|
||||
func newGRPCProxyServer(lg *zap.Logger, client *clientv3.Client) *grpc.Server {
|
||||
if grpcProxyEnableOrdering {
|
||||
vf := ordering.NewOrderViolationSwitchEndpointClosure(*client)
|
||||
client.KV = ordering.NewKV(client.KV, vf)
|
||||
lg.Info("waiting for linearized read from cluster to recover ordering")
|
||||
for {
|
||||
_, err := client.KV.Get(context.TODO(), "_", clientv3.WithKeysOnly())
|
||||
if err == nil {
|
||||
break
|
||||
}
|
||||
lg.Warn("ordering recovery failed, retrying in 1s", zap.Error(err))
|
||||
time.Sleep(time.Second)
|
||||
}
|
||||
}
|
||||
|
||||
if len(grpcProxyNamespace) > 0 {
|
||||
client.KV = namespace.NewKV(client.KV, grpcProxyNamespace)
|
||||
client.Watcher = namespace.NewWatcher(client.Watcher, grpcProxyNamespace)
|
||||
client.Lease = namespace.NewLease(client.Lease, grpcProxyNamespace)
|
||||
}
|
||||
|
||||
if len(grpcProxyLeasing) > 0 {
|
||||
client.KV, _, _ = leasing.NewKV(client, grpcProxyLeasing)
|
||||
}
|
||||
|
||||
kvp, _ := grpcproxy.NewKvProxy(client)
|
||||
watchp, _ := grpcproxy.NewWatchProxy(client.Ctx(), lg, client)
|
||||
if grpcProxyResolverPrefix != "" {
|
||||
grpcproxy.Register(lg, client, grpcProxyResolverPrefix, grpcProxyAdvertiseClientURL, grpcProxyResolverTTL)
|
||||
}
|
||||
clusterp, _ := grpcproxy.NewClusterProxy(lg, client, grpcProxyAdvertiseClientURL, grpcProxyResolverPrefix)
|
||||
leasep, _ := grpcproxy.NewLeaseProxy(client.Ctx(), client)
|
||||
|
||||
mainp := grpcproxy.NewMaintenanceProxy(client)
|
||||
authp := grpcproxy.NewAuthProxy(client)
|
||||
electionp := grpcproxy.NewElectionProxy(client)
|
||||
lockp := grpcproxy.NewLockProxy(client)
|
||||
|
||||
gopts := []grpc.ServerOption{
|
||||
grpc.StreamInterceptor(grpc_prometheus.StreamServerInterceptor),
|
||||
grpc.UnaryInterceptor(grpc_prometheus.UnaryServerInterceptor),
|
||||
grpc.MaxConcurrentStreams(math.MaxUint32),
|
||||
}
|
||||
if grpcKeepAliveMinTime > time.Duration(0) {
|
||||
gopts = append(gopts, grpc.KeepaliveEnforcementPolicy(keepalive.EnforcementPolicy{
|
||||
MinTime: grpcKeepAliveMinTime,
|
||||
PermitWithoutStream: false,
|
||||
}))
|
||||
}
|
||||
if grpcKeepAliveInterval > time.Duration(0) ||
|
||||
grpcKeepAliveTimeout > time.Duration(0) {
|
||||
gopts = append(gopts, grpc.KeepaliveParams(keepalive.ServerParameters{
|
||||
Time: grpcKeepAliveInterval,
|
||||
Timeout: grpcKeepAliveTimeout,
|
||||
}))
|
||||
}
|
||||
|
||||
server := grpc.NewServer(gopts...)
|
||||
|
||||
pb.RegisterKVServer(server, kvp)
|
||||
pb.RegisterWatchServer(server, watchp)
|
||||
pb.RegisterClusterServer(server, clusterp)
|
||||
pb.RegisterLeaseServer(server, leasep)
|
||||
pb.RegisterMaintenanceServer(server, mainp)
|
||||
pb.RegisterAuthServer(server, authp)
|
||||
v3electionpb.RegisterElectionServer(server, electionp)
|
||||
v3lockpb.RegisterLockServer(server, lockp)
|
||||
|
||||
return server
|
||||
}
|
||||
|
||||
func mustHTTPListener(lg *zap.Logger, m cmux.CMux, tlsinfo *transport.TLSInfo, c *clientv3.Client, proxy *clientv3.Client) (*http.Server, net.Listener) {
|
||||
httpClient := mustNewHTTPClient(lg)
|
||||
httpmux := http.NewServeMux()
|
||||
httpmux.HandleFunc("/", http.NotFound)
|
||||
grpcproxy.HandleMetrics(httpmux, httpClient, c.Endpoints())
|
||||
grpcproxy.HandleHealth(lg, httpmux, c)
|
||||
grpcproxy.HandleProxyMetrics(httpmux)
|
||||
grpcproxy.HandleProxyHealth(lg, httpmux, proxy)
|
||||
if grpcProxyEnablePprof {
|
||||
for p, h := range debugutil.PProfHandlers() {
|
||||
httpmux.Handle(p, h)
|
||||
}
|
||||
lg.Info("gRPC proxy enabled pprof", zap.String("path", debugutil.HTTPPrefixPProf))
|
||||
}
|
||||
srvhttp := &http.Server{
|
||||
Handler: httpmux,
|
||||
ErrorLog: log.New(ioutil.Discard, "net/http", 0),
|
||||
}
|
||||
|
||||
if tlsinfo == nil {
|
||||
return srvhttp, m.Match(cmux.HTTP1())
|
||||
}
|
||||
|
||||
srvTLS, err := tlsinfo.ServerConfig()
|
||||
if err != nil {
|
||||
lg.Fatal("failed to set up TLS", zap.Error(err))
|
||||
}
|
||||
srvhttp.TLSConfig = srvTLS
|
||||
return srvhttp, m.Match(cmux.Any())
|
||||
}
|
||||
|
||||
func mustNewHTTPClient(lg *zap.Logger) *http.Client {
|
||||
transport, err := newHTTPTransport(grpcProxyCA, grpcProxyCert, grpcProxyKey)
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
return &http.Client{Transport: transport}
|
||||
}
|
||||
|
||||
func newHTTPTransport(ca, cert, key string) (*http.Transport, error) {
|
||||
tr := &http.Transport{}
|
||||
|
||||
if ca != "" && cert != "" && key != "" {
|
||||
caCert, err := ioutil.ReadFile(ca)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
keyPair, err := tls.LoadX509KeyPair(cert, key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
caPool := x509.NewCertPool()
|
||||
caPool.AppendCertsFromPEM(caCert)
|
||||
|
||||
tlsConfig := &tls.Config{
|
||||
Certificates: []tls.Certificate{keyPair},
|
||||
RootCAs: caPool,
|
||||
}
|
||||
tlsConfig.BuildNameToCertificate()
|
||||
tr.TLSClientConfig = tlsConfig
|
||||
} else if grpcProxyInsecureSkipTLSVerify {
|
||||
tlsConfig := &tls.Config{InsecureSkipVerify: grpcProxyInsecureSkipTLSVerify}
|
||||
tr.TLSClientConfig = tlsConfig
|
||||
}
|
||||
return tr, nil
|
||||
}
|
||||
|
||||
func mustMetricsListener(lg *zap.Logger, tlsinfo *transport.TLSInfo) net.Listener {
|
||||
murl, err := url.Parse(grpcProxyMetricsListenAddr)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "cannot parse %q", grpcProxyMetricsListenAddr)
|
||||
os.Exit(1)
|
||||
}
|
||||
ml, err := transport.NewListener(murl.Host, murl.Scheme, tlsinfo)
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
lg.Info("gRPC proxy listening for metrics", zap.String("address", murl.String()))
|
||||
return ml
|
||||
}
|
||||
226
server/etcdmain/help.go
Normal file
226
server/etcdmain/help.go
Normal file
@@ -0,0 +1,226 @@
|
||||
// Copyright 2015 The etcd Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package etcdmain
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"go.etcd.io/etcd/v3/embed"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
)
|
||||
|
||||
var (
|
||||
usageline = `Usage:
|
||||
|
||||
etcd [flags]
|
||||
Start an etcd server.
|
||||
|
||||
etcd --version
|
||||
Show the version of etcd.
|
||||
|
||||
etcd -h | --help
|
||||
Show the help information about etcd.
|
||||
|
||||
etcd --config-file
|
||||
Path to the server configuration file. Note that if a configuration file is provided, other command line flags and environment variables will be ignored.
|
||||
|
||||
etcd gateway
|
||||
Run the stateless pass-through etcd TCP connection forwarding proxy.
|
||||
|
||||
etcd grpc-proxy
|
||||
Run the stateless etcd v3 gRPC L7 reverse proxy.
|
||||
`
|
||||
flagsline = `
|
||||
Member:
|
||||
--name 'default'
|
||||
Human-readable name for this member.
|
||||
--data-dir '${name}.etcd'
|
||||
Path to the data directory.
|
||||
--wal-dir ''
|
||||
Path to the dedicated wal directory.
|
||||
--snapshot-count '100000'
|
||||
Number of committed transactions to trigger a snapshot to disk.
|
||||
--heartbeat-interval '100'
|
||||
Time (in milliseconds) of a heartbeat interval.
|
||||
--election-timeout '1000'
|
||||
Time (in milliseconds) for an election to timeout. See tuning documentation for details.
|
||||
--initial-election-tick-advance 'true'
|
||||
Whether to fast-forward initial election ticks on boot for faster election.
|
||||
--listen-peer-urls 'http://localhost:2380'
|
||||
List of URLs to listen on for peer traffic.
|
||||
--listen-client-urls 'http://localhost:2379'
|
||||
List of URLs to listen on for client traffic.
|
||||
--max-snapshots '` + strconv.Itoa(embed.DefaultMaxSnapshots) + `'
|
||||
Maximum number of snapshot files to retain (0 is unlimited).
|
||||
--max-wals '` + strconv.Itoa(embed.DefaultMaxWALs) + `'
|
||||
Maximum number of wal files to retain (0 is unlimited).
|
||||
--quota-backend-bytes '0'
|
||||
Raise alarms when backend size exceeds the given quota (0 defaults to low space quota).
|
||||
--backend-bbolt-freelist-type 'map'
|
||||
BackendFreelistType specifies the type of freelist that boltdb backend uses(array and map are supported types).
|
||||
--backend-batch-interval ''
|
||||
BackendBatchInterval is the maximum time before commit the backend transaction.
|
||||
--backend-batch-limit '0'
|
||||
BackendBatchLimit is the maximum operations before commit the backend transaction.
|
||||
--max-txn-ops '128'
|
||||
Maximum number of operations permitted in a transaction.
|
||||
--max-request-bytes '1572864'
|
||||
Maximum client request size in bytes the server will accept.
|
||||
--grpc-keepalive-min-time '5s'
|
||||
Minimum duration interval that a client should wait before pinging server.
|
||||
--grpc-keepalive-interval '2h'
|
||||
Frequency duration of server-to-client ping to check if a connection is alive (0 to disable).
|
||||
--grpc-keepalive-timeout '20s'
|
||||
Additional duration of wait before closing a non-responsive connection (0 to disable).
|
||||
|
||||
Clustering:
|
||||
--initial-advertise-peer-urls 'http://localhost:2380'
|
||||
List of this member's peer URLs to advertise to the rest of the cluster.
|
||||
--initial-cluster 'default=http://localhost:2380'
|
||||
Initial cluster configuration for bootstrapping.
|
||||
--initial-cluster-state 'new'
|
||||
Initial cluster state ('new' or 'existing').
|
||||
--initial-cluster-token 'etcd-cluster'
|
||||
Initial cluster token for the etcd cluster during bootstrap.
|
||||
Specifying this can protect you from unintended cross-cluster interaction when running multiple clusters.
|
||||
--advertise-client-urls 'http://localhost:2379'
|
||||
List of this member's client URLs to advertise to the public.
|
||||
The client URLs advertised should be accessible to machines that talk to etcd cluster. etcd client libraries parse these URLs to connect to the cluster.
|
||||
--discovery ''
|
||||
Discovery URL used to bootstrap the cluster.
|
||||
--discovery-fallback 'proxy'
|
||||
Expected behavior ('exit' or 'proxy') when discovery services fails.
|
||||
"proxy" supports v2 API only.
|
||||
--discovery-proxy ''
|
||||
HTTP proxy to use for traffic to discovery service.
|
||||
--discovery-srv ''
|
||||
DNS srv domain used to bootstrap the cluster.
|
||||
--discovery-srv-name ''
|
||||
Suffix to the dns srv name queried when bootstrapping.
|
||||
--strict-reconfig-check '` + strconv.FormatBool(embed.DefaultStrictReconfigCheck) + `'
|
||||
Reject reconfiguration requests that would cause quorum loss.
|
||||
--pre-vote 'false'
|
||||
Enable to run an additional Raft election phase.
|
||||
--auto-compaction-retention '0'
|
||||
Auto compaction retention length. 0 means disable auto compaction.
|
||||
--auto-compaction-mode 'periodic'
|
||||
Interpret 'auto-compaction-retention' one of: periodic|revision. 'periodic' for duration based retention, defaulting to hours if no time unit is provided (e.g. '5m'). 'revision' for revision number based retention.
|
||||
--enable-v2 '` + strconv.FormatBool(embed.DefaultEnableV2) + `'
|
||||
Accept etcd V2 client requests.
|
||||
|
||||
Security:
|
||||
--cert-file ''
|
||||
Path to the client server TLS cert file.
|
||||
--key-file ''
|
||||
Path to the client server TLS key file.
|
||||
--client-cert-auth 'false'
|
||||
Enable client cert authentication.
|
||||
--client-crl-file ''
|
||||
Path to the client certificate revocation list file.
|
||||
--client-cert-allowed-hostname ''
|
||||
Allowed TLS hostname for client cert authentication.
|
||||
--trusted-ca-file ''
|
||||
Path to the client server TLS trusted CA cert file.
|
||||
--auto-tls 'false'
|
||||
Client TLS using generated certificates.
|
||||
--peer-cert-file ''
|
||||
Path to the peer server TLS cert file.
|
||||
--peer-key-file ''
|
||||
Path to the peer server TLS key file.
|
||||
--peer-client-cert-auth 'false'
|
||||
Enable peer client cert authentication.
|
||||
--peer-trusted-ca-file ''
|
||||
Path to the peer server TLS trusted CA file.
|
||||
--peer-cert-allowed-cn ''
|
||||
Required CN for client certs connecting to the peer endpoint.
|
||||
--peer-cert-allowed-hostname ''
|
||||
Allowed TLS hostname for inter peer authentication.
|
||||
--peer-auto-tls 'false'
|
||||
Peer TLS using self-generated certificates if --peer-key-file and --peer-cert-file are not provided.
|
||||
--peer-crl-file ''
|
||||
Path to the peer certificate revocation list file.
|
||||
--cipher-suites ''
|
||||
Comma-separated list of supported TLS cipher suites between client/server and peers (empty will be auto-populated by Go).
|
||||
--cors '*'
|
||||
Comma-separated whitelist of origins for CORS, or cross-origin resource sharing, (empty or * means allow all).
|
||||
--host-whitelist '*'
|
||||
Acceptable hostnames from HTTP client requests, if server is not secure (empty or * means allow all).
|
||||
|
||||
Auth:
|
||||
--auth-token 'simple'
|
||||
Specify a v3 authentication token type and its options ('simple' or 'jwt').
|
||||
--bcrypt-cost ` + fmt.Sprintf("%d", bcrypt.DefaultCost) + `
|
||||
Specify the cost / strength of the bcrypt algorithm for hashing auth passwords. Valid values are between ` + fmt.Sprintf("%d", bcrypt.MinCost) + ` and ` + fmt.Sprintf("%d", bcrypt.MaxCost) + `.
|
||||
--auth-token-ttl 300
|
||||
Time (in seconds) of the auth-token-ttl.
|
||||
|
||||
Profiling and Monitoring:
|
||||
--enable-pprof 'false'
|
||||
Enable runtime profiling data via HTTP server. Address is at client URL + "/debug/pprof/"
|
||||
--metrics 'basic'
|
||||
Set level of detail for exported metrics, specify 'extensive' to include server side grpc histogram metrics.
|
||||
--listen-metrics-urls ''
|
||||
List of URLs to listen on for the metrics and health endpoints.
|
||||
|
||||
Logging:
|
||||
--logger 'zap'
|
||||
Currently only supports 'zap' for structured logging.
|
||||
--log-outputs 'default'
|
||||
Specify 'stdout' or 'stderr' to skip journald logging even when running under systemd, or list of comma separated output targets.
|
||||
--log-level 'info'
|
||||
Configures log level. Only supports debug, info, warn, error, panic, or fatal.
|
||||
|
||||
v2 Proxy (to be deprecated in v4):
|
||||
--proxy 'off'
|
||||
Proxy mode setting ('off', 'readonly' or 'on').
|
||||
--proxy-failure-wait 5000
|
||||
Time (in milliseconds) an endpoint will be held in a failed state.
|
||||
--proxy-refresh-interval 30000
|
||||
Time (in milliseconds) of the endpoints refresh interval.
|
||||
--proxy-dial-timeout 1000
|
||||
Time (in milliseconds) for a dial to timeout.
|
||||
--proxy-write-timeout 5000
|
||||
Time (in milliseconds) for a write to timeout.
|
||||
--proxy-read-timeout 0
|
||||
Time (in milliseconds) for a read to timeout.
|
||||
|
||||
Experimental feature:
|
||||
--experimental-initial-corrupt-check 'false'
|
||||
Enable to check data corruption before serving any client/peer traffic.
|
||||
--experimental-corrupt-check-time '0s'
|
||||
Duration of time between cluster corruption check passes.
|
||||
--experimental-enable-v2v3 ''
|
||||
Serve v2 requests through the v3 backend under a given prefix.
|
||||
--experimental-enable-lease-checkpoint 'false'
|
||||
ExperimentalEnableLeaseCheckpoint enables primary lessor to persist lease remainingTTL to prevent indefinite auto-renewal of long lived leases.
|
||||
--experimental-compaction-batch-limit 1000
|
||||
ExperimentalCompactionBatchLimit sets the maximum revisions deleted in each compaction batch.
|
||||
--experimental-peer-skip-client-san-verification 'false'
|
||||
Skip verification of SAN field in client certificate for peer connections.
|
||||
--experimental-watch-progress-notify-interval '10m'
|
||||
Duration of periodical watch progress notification.
|
||||
|
||||
Unsafe feature:
|
||||
--force-new-cluster 'false'
|
||||
Force to create a new one-member cluster.
|
||||
--unsafe-no-fsync 'false'
|
||||
Disables fsync, unsafe, will cause data loss.
|
||||
|
||||
CAUTIOUS with unsafe flag! It may break the guarantees given by the consensus protocol!
|
||||
`
|
||||
)
|
||||
|
||||
// Add back "TO BE DEPRECATED" section if needed
|
||||
54
server/etcdmain/main.go
Normal file
54
server/etcdmain/main.go
Normal file
@@ -0,0 +1,54 @@
|
||||
// Copyright 2015 The etcd Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package etcdmain
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/coreos/go-systemd/v22/daemon"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
func Main(args []string) {
|
||||
checkSupportArch()
|
||||
|
||||
if len(args) > 1 {
|
||||
cmd := args[1]
|
||||
switch cmd {
|
||||
case "gateway", "grpc-proxy":
|
||||
if err := rootCmd.Execute(); err != nil {
|
||||
fmt.Fprint(os.Stderr, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
startEtcdOrProxyV2(args)
|
||||
}
|
||||
|
||||
func notifySystemd(lg *zap.Logger) {
|
||||
if lg == nil {
|
||||
lg = zap.NewExample()
|
||||
}
|
||||
lg.Info("notifying init daemon")
|
||||
_, err := daemon.SdNotify(false, daemon.SdNotifyReady)
|
||||
if err != nil {
|
||||
lg.Error("failed to notify systemd for readiness", zap.Error(err))
|
||||
return
|
||||
}
|
||||
lg.Info("successfully notified init daemon")
|
||||
}
|
||||
97
server/etcdmain/util.go
Normal file
97
server/etcdmain/util.go
Normal file
@@ -0,0 +1,97 @@
|
||||
// Copyright 2017 The etcd Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package etcdmain
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"go.etcd.io/etcd/pkg/v3/srv"
|
||||
"go.etcd.io/etcd/pkg/v3/transport"
|
||||
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
func discoverEndpoints(lg *zap.Logger, dns string, ca string, insecure bool, serviceName string) (s srv.SRVClients) {
|
||||
if dns == "" {
|
||||
return s
|
||||
}
|
||||
srvs, err := srv.GetClient("etcd-client", dns, serviceName)
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
endpoints := srvs.Endpoints
|
||||
|
||||
if lg != nil {
|
||||
lg.Info(
|
||||
"discovered cluster from SRV",
|
||||
zap.String("srv-server", dns),
|
||||
zap.Strings("endpoints", endpoints),
|
||||
)
|
||||
}
|
||||
|
||||
if insecure {
|
||||
return *srvs
|
||||
}
|
||||
// confirm TLS connections are good
|
||||
tlsInfo := transport.TLSInfo{
|
||||
TrustedCAFile: ca,
|
||||
ServerName: dns,
|
||||
}
|
||||
|
||||
if lg != nil {
|
||||
lg.Info(
|
||||
"validating discovered SRV endpoints",
|
||||
zap.String("srv-server", dns),
|
||||
zap.Strings("endpoints", endpoints),
|
||||
)
|
||||
}
|
||||
|
||||
endpoints, err = transport.ValidateSecureEndpoints(tlsInfo, endpoints)
|
||||
if err != nil {
|
||||
if lg != nil {
|
||||
lg.Warn(
|
||||
"failed to validate discovered endpoints",
|
||||
zap.String("srv-server", dns),
|
||||
zap.Strings("endpoints", endpoints),
|
||||
zap.Error(err),
|
||||
)
|
||||
}
|
||||
} else {
|
||||
if lg != nil {
|
||||
lg.Info(
|
||||
"using validated discovered SRV endpoints",
|
||||
zap.String("srv-server", dns),
|
||||
zap.Strings("endpoints", endpoints),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// map endpoints back to SRVClients struct with SRV data
|
||||
eps := make(map[string]struct{})
|
||||
for _, ep := range endpoints {
|
||||
eps[ep] = struct{}{}
|
||||
}
|
||||
for i := range srvs.Endpoints {
|
||||
if _, ok := eps[srvs.Endpoints[i]]; !ok {
|
||||
continue
|
||||
}
|
||||
s.Endpoints = append(s.Endpoints, srvs.Endpoints[i])
|
||||
s.SRVs = append(s.SRVs, srvs.SRVs[i])
|
||||
}
|
||||
|
||||
return s
|
||||
}
|
||||
Reference in New Issue
Block a user