config.go 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500
  1. package config
  2. import (
  3. "flag"
  4. "fmt"
  5. "io/ioutil"
  6. "math/rand"
  7. "net"
  8. "net/url"
  9. "os"
  10. "path/filepath"
  11. "reflect"
  12. "strconv"
  13. "strings"
  14. "time"
  15. "github.com/coreos/etcd/third_party/github.com/BurntSushi/toml"
  16. "github.com/coreos/etcd/log"
  17. ustrings "github.com/coreos/etcd/pkg/strings"
  18. "github.com/coreos/etcd/server"
  19. )
  20. // The default location for the etcd configuration file.
  21. const DefaultSystemConfigPath = "/etc/etcd/etcd.conf"
  22. // A lookup of deprecated flags to their new flag name.
  23. var newFlagNameLookup = map[string]string{
  24. "C": "peers",
  25. "CF": "peers-file",
  26. "n": "name",
  27. "c": "addr",
  28. "cl": "bind-addr",
  29. "s": "peer-addr",
  30. "sl": "peer-bind-addr",
  31. "d": "data-dir",
  32. "m": "max-result-buffer",
  33. "r": "max-retry-attempts",
  34. "maxsize": "cluster-active-size",
  35. "clientCAFile": "ca-file",
  36. "clientCert": "cert-file",
  37. "clientKey": "key-file",
  38. "serverCAFile": "peer-ca-file",
  39. "serverCert": "peer-cert-file",
  40. "serverKey": "peer-key-file",
  41. "snapshotCount": "snapshot-count",
  42. "peer-heartbeat-timeout": "peer-heartbeat-interval",
  43. "max-cluster-size": "cluster-active-size",
  44. }
  45. // Config represents the server configuration.
  46. type Config struct {
  47. SystemPath string
  48. Addr string `toml:"addr" env:"ETCD_ADDR"`
  49. BindAddr string `toml:"bind_addr" env:"ETCD_BIND_ADDR"`
  50. CAFile string `toml:"ca_file" env:"ETCD_CA_FILE"`
  51. CertFile string `toml:"cert_file" env:"ETCD_CERT_FILE"`
  52. CPUProfileFile string
  53. CorsOrigins []string `toml:"cors" env:"ETCD_CORS"`
  54. DataDir string `toml:"data_dir" env:"ETCD_DATA_DIR"`
  55. Discovery string `toml:"discovery" env:"ETCD_DISCOVERY"`
  56. Force bool
  57. KeyFile string `toml:"key_file" env:"ETCD_KEY_FILE"`
  58. HTTPReadTimeout float64 `toml:"http_read_timeout" env:"ETCD_HTTP_READ_TIMEOUT"`
  59. HTTPWriteTimeout float64 `toml:"http_write_timeout" env:"ETCD_HTTP_WRITE_TIMEOUT"`
  60. Peers []string `toml:"peers" env:"ETCD_PEERS"`
  61. PeersFile string `toml:"peers_file" env:"ETCD_PEERS_FILE"`
  62. MaxResultBuffer int `toml:"max_result_buffer" env:"ETCD_MAX_RESULT_BUFFER"`
  63. MaxRetryAttempts int `toml:"max_retry_attempts" env:"ETCD_MAX_RETRY_ATTEMPTS"`
  64. RetryInterval float64 `toml:"retry_interval" env:"ETCD_RETRY_INTERVAL"`
  65. Name string `toml:"name" env:"ETCD_NAME"`
  66. Snapshot bool `toml:"snapshot" env:"ETCD_SNAPSHOT"`
  67. SnapshotCount int `toml:"snapshot_count" env:"ETCD_SNAPSHOTCOUNT"`
  68. ShowHelp bool
  69. ShowVersion bool
  70. Verbose bool `toml:"verbose" env:"ETCD_VERBOSE"`
  71. VeryVerbose bool `toml:"very_verbose" env:"ETCD_VERY_VERBOSE"`
  72. VeryVeryVerbose bool `toml:"very_very_verbose" env:"ETCD_VERY_VERY_VERBOSE"`
  73. Peer struct {
  74. Addr string `toml:"addr" env:"ETCD_PEER_ADDR"`
  75. BindAddr string `toml:"bind_addr" env:"ETCD_PEER_BIND_ADDR"`
  76. CAFile string `toml:"ca_file" env:"ETCD_PEER_CA_FILE"`
  77. CertFile string `toml:"cert_file" env:"ETCD_PEER_CERT_FILE"`
  78. KeyFile string `toml:"key_file" env:"ETCD_PEER_KEY_FILE"`
  79. HeartbeatInterval int `toml:"heartbeat_interval" env:"ETCD_PEER_HEARTBEAT_INTERVAL"`
  80. ElectionTimeout int `toml:"election_timeout" env:"ETCD_PEER_ELECTION_TIMEOUT"`
  81. }
  82. StrTrace string `toml:"trace" env:"ETCD_TRACE"`
  83. GraphiteHost string `toml:"graphite_host" env:"ETCD_GRAPHITE_HOST"`
  84. Cluster struct {
  85. ActiveSize int `toml:"active_size" env:"ETCD_CLUSTER_ACTIVE_SIZE"`
  86. RemoveDelay float64 `toml:"remove_delay" env:"ETCD_CLUSTER_REMOVE_DELAY"`
  87. SyncInterval float64 `toml:"sync_interval" env:"ETCD_CLUSTER_SYNC_INTERVAL"`
  88. }
  89. }
  90. // New returns a Config initialized with default values.
  91. func New() *Config {
  92. c := new(Config)
  93. c.SystemPath = DefaultSystemConfigPath
  94. c.Addr = "127.0.0.1:4001"
  95. c.HTTPReadTimeout = server.DefaultReadTimeout
  96. c.HTTPWriteTimeout = server.DefaultWriteTimeout
  97. c.MaxResultBuffer = 1024
  98. c.MaxRetryAttempts = 3
  99. c.RetryInterval = 10.0
  100. c.Snapshot = true
  101. c.SnapshotCount = 10000
  102. c.Peer.Addr = "127.0.0.1:7001"
  103. c.Peer.HeartbeatInterval = defaultHeartbeatInterval
  104. c.Peer.ElectionTimeout = defaultElectionTimeout
  105. rand.Seed(time.Now().UTC().UnixNano())
  106. // Make maximum twice as minimum.
  107. c.RetryInterval = float64(50+rand.Int()%50) * defaultHeartbeatInterval / 1000
  108. c.Cluster.ActiveSize = server.DefaultActiveSize
  109. c.Cluster.RemoveDelay = server.DefaultRemoveDelay
  110. c.Cluster.SyncInterval = server.DefaultSyncInterval
  111. return c
  112. }
  113. // Loads the configuration from the system config, command line config,
  114. // environment variables, and finally command line arguments.
  115. func (c *Config) Load(arguments []string) error {
  116. var path string
  117. f := flag.NewFlagSet("etcd", -1)
  118. f.SetOutput(ioutil.Discard)
  119. f.StringVar(&path, "config", "", "path to config file")
  120. f.Parse(arguments)
  121. // Load from system file.
  122. if err := c.LoadSystemFile(); err != nil {
  123. return err
  124. }
  125. // Load from config file specified in arguments.
  126. if path != "" {
  127. if err := c.LoadFile(path); err != nil {
  128. return err
  129. }
  130. }
  131. // Load from the environment variables next.
  132. if err := c.LoadEnv(); err != nil {
  133. return err
  134. }
  135. // Load from command line flags.
  136. if err := c.LoadFlags(arguments); err != nil {
  137. return err
  138. }
  139. // Loads peers if a peer file was specified.
  140. if err := c.LoadPeersFile(); err != nil {
  141. return err
  142. }
  143. return nil
  144. }
  145. // Loads from the system etcd configuration file if it exists.
  146. func (c *Config) LoadSystemFile() error {
  147. if _, err := os.Stat(c.SystemPath); os.IsNotExist(err) {
  148. return nil
  149. }
  150. return c.LoadFile(c.SystemPath)
  151. }
  152. // Loads configuration from a file.
  153. func (c *Config) LoadFile(path string) error {
  154. _, err := toml.DecodeFile(path, &c)
  155. return err
  156. }
  157. // LoadEnv loads the configuration via environment variables.
  158. func (c *Config) LoadEnv() error {
  159. if err := c.loadEnv(c); err != nil {
  160. return err
  161. }
  162. if err := c.loadEnv(&c.Peer); err != nil {
  163. return err
  164. }
  165. if err := c.loadEnv(&c.Cluster); err != nil {
  166. return err
  167. }
  168. return nil
  169. }
  170. func (c *Config) loadEnv(target interface{}) error {
  171. value := reflect.Indirect(reflect.ValueOf(target))
  172. typ := value.Type()
  173. for i := 0; i < typ.NumField(); i++ {
  174. field := typ.Field(i)
  175. // Retrieve environment variable.
  176. v := strings.TrimSpace(os.Getenv(field.Tag.Get("env")))
  177. if v == "" {
  178. continue
  179. }
  180. // Set the appropriate type.
  181. switch field.Type.Kind() {
  182. case reflect.Bool:
  183. value.Field(i).SetBool(v != "0" && v != "false")
  184. case reflect.Int:
  185. newValue, err := strconv.ParseInt(v, 10, 0)
  186. if err != nil {
  187. return fmt.Errorf("Parse error: %s: %s", field.Tag.Get("env"), err)
  188. }
  189. value.Field(i).SetInt(newValue)
  190. case reflect.String:
  191. value.Field(i).SetString(v)
  192. case reflect.Slice:
  193. value.Field(i).Set(reflect.ValueOf(ustrings.TrimSplit(v, ",")))
  194. case reflect.Float64:
  195. newValue, err := strconv.ParseFloat(v, 64)
  196. if err != nil {
  197. return fmt.Errorf("Parse error: %s: %s", field.Tag.Get("env"), err)
  198. }
  199. value.Field(i).SetFloat(newValue)
  200. }
  201. }
  202. return nil
  203. }
  204. // Loads configuration from command line flags.
  205. func (c *Config) LoadFlags(arguments []string) error {
  206. var peers, cors, path string
  207. f := flag.NewFlagSet(os.Args[0], flag.ContinueOnError)
  208. f.SetOutput(ioutil.Discard)
  209. f.BoolVar(&c.ShowHelp, "h", false, "")
  210. f.BoolVar(&c.ShowHelp, "help", false, "")
  211. f.BoolVar(&c.ShowVersion, "version", false, "")
  212. f.BoolVar(&c.Force, "f", false, "")
  213. f.BoolVar(&c.Force, "force", false, "")
  214. f.BoolVar(&c.Verbose, "v", c.Verbose, "")
  215. f.BoolVar(&c.VeryVerbose, "vv", c.VeryVerbose, "")
  216. f.BoolVar(&c.VeryVeryVerbose, "vvv", c.VeryVeryVerbose, "")
  217. f.StringVar(&peers, "peers", "", "")
  218. f.StringVar(&c.PeersFile, "peers-file", c.PeersFile, "")
  219. f.StringVar(&c.Name, "name", c.Name, "")
  220. f.StringVar(&c.Addr, "addr", c.Addr, "")
  221. f.StringVar(&c.Discovery, "discovery", c.Discovery, "")
  222. f.StringVar(&c.BindAddr, "bind-addr", c.BindAddr, "")
  223. f.StringVar(&c.Peer.Addr, "peer-addr", c.Peer.Addr, "")
  224. f.StringVar(&c.Peer.BindAddr, "peer-bind-addr", c.Peer.BindAddr, "")
  225. f.StringVar(&c.CAFile, "ca-file", c.CAFile, "")
  226. f.StringVar(&c.CertFile, "cert-file", c.CertFile, "")
  227. f.StringVar(&c.KeyFile, "key-file", c.KeyFile, "")
  228. f.StringVar(&c.Peer.CAFile, "peer-ca-file", c.Peer.CAFile, "")
  229. f.StringVar(&c.Peer.CertFile, "peer-cert-file", c.Peer.CertFile, "")
  230. f.StringVar(&c.Peer.KeyFile, "peer-key-file", c.Peer.KeyFile, "")
  231. f.Float64Var(&c.HTTPReadTimeout, "http-read-timeout", c.HTTPReadTimeout, "")
  232. f.Float64Var(&c.HTTPWriteTimeout, "http-write-timeout", c.HTTPReadTimeout, "")
  233. f.StringVar(&c.DataDir, "data-dir", c.DataDir, "")
  234. f.IntVar(&c.MaxResultBuffer, "max-result-buffer", c.MaxResultBuffer, "")
  235. f.IntVar(&c.MaxRetryAttempts, "max-retry-attempts", c.MaxRetryAttempts, "")
  236. f.Float64Var(&c.RetryInterval, "retry-interval", c.RetryInterval, "")
  237. f.IntVar(&c.Peer.HeartbeatInterval, "peer-heartbeat-interval", c.Peer.HeartbeatInterval, "")
  238. f.IntVar(&c.Peer.ElectionTimeout, "peer-election-timeout", c.Peer.ElectionTimeout, "")
  239. f.StringVar(&cors, "cors", "", "")
  240. f.BoolVar(&c.Snapshot, "snapshot", c.Snapshot, "")
  241. f.IntVar(&c.SnapshotCount, "snapshot-count", c.SnapshotCount, "")
  242. f.StringVar(&c.CPUProfileFile, "cpuprofile", "", "")
  243. f.StringVar(&c.StrTrace, "trace", "", "")
  244. f.StringVar(&c.GraphiteHost, "graphite-host", "", "")
  245. f.IntVar(&c.Cluster.ActiveSize, "cluster-active-size", c.Cluster.ActiveSize, "")
  246. f.Float64Var(&c.Cluster.RemoveDelay, "cluster-remove-delay", c.Cluster.RemoveDelay, "")
  247. f.Float64Var(&c.Cluster.SyncInterval, "cluster-sync-interval", c.Cluster.SyncInterval, "")
  248. // BEGIN IGNORED FLAGS
  249. f.StringVar(&path, "config", "", "")
  250. // BEGIN IGNORED FLAGS
  251. // BEGIN DEPRECATED FLAGS
  252. f.StringVar(&peers, "C", "", "(deprecated)")
  253. f.StringVar(&c.PeersFile, "CF", c.PeersFile, "(deprecated)")
  254. f.StringVar(&c.Name, "n", c.Name, "(deprecated)")
  255. f.StringVar(&c.Addr, "c", c.Addr, "(deprecated)")
  256. f.StringVar(&c.BindAddr, "cl", c.BindAddr, "(deprecated)")
  257. f.StringVar(&c.Peer.Addr, "s", c.Peer.Addr, "(deprecated)")
  258. f.StringVar(&c.Peer.BindAddr, "sl", c.Peer.BindAddr, "(deprecated)")
  259. f.StringVar(&c.Peer.CAFile, "serverCAFile", c.Peer.CAFile, "(deprecated)")
  260. f.StringVar(&c.Peer.CertFile, "serverCert", c.Peer.CertFile, "(deprecated)")
  261. f.StringVar(&c.Peer.KeyFile, "serverKey", c.Peer.KeyFile, "(deprecated)")
  262. f.StringVar(&c.CAFile, "clientCAFile", c.CAFile, "(deprecated)")
  263. f.StringVar(&c.CertFile, "clientCert", c.CertFile, "(deprecated)")
  264. f.StringVar(&c.KeyFile, "clientKey", c.KeyFile, "(deprecated)")
  265. f.StringVar(&c.DataDir, "d", c.DataDir, "(deprecated)")
  266. f.IntVar(&c.MaxResultBuffer, "m", c.MaxResultBuffer, "(deprecated)")
  267. f.IntVar(&c.MaxRetryAttempts, "r", c.MaxRetryAttempts, "(deprecated)")
  268. f.IntVar(&c.SnapshotCount, "snapshotCount", c.SnapshotCount, "(deprecated)")
  269. f.IntVar(&c.Peer.HeartbeatInterval, "peer-heartbeat-timeout", c.Peer.HeartbeatInterval, "(deprecated)")
  270. f.IntVar(&c.Cluster.ActiveSize, "max-cluster-size", c.Cluster.ActiveSize, "(deprecated)")
  271. f.IntVar(&c.Cluster.ActiveSize, "maxsize", c.Cluster.ActiveSize, "(deprecated)")
  272. // END DEPRECATED FLAGS
  273. if err := f.Parse(arguments); err != nil {
  274. return err
  275. }
  276. // Print deprecation warnings on STDERR.
  277. f.Visit(func(f *flag.Flag) {
  278. if len(newFlagNameLookup[f.Name]) > 0 {
  279. fmt.Fprintf(os.Stderr, "[deprecated] use -%s, not -%s\n", newFlagNameLookup[f.Name], f.Name)
  280. }
  281. })
  282. // Convert some parameters to lists.
  283. if peers != "" {
  284. c.Peers = ustrings.TrimSplit(peers, ",")
  285. }
  286. if cors != "" {
  287. c.CorsOrigins = ustrings.TrimSplit(cors, ",")
  288. }
  289. return nil
  290. }
  291. // LoadPeersFile loads the peers listed in the peers file.
  292. func (c *Config) LoadPeersFile() error {
  293. if c.PeersFile == "" {
  294. return nil
  295. }
  296. b, err := ioutil.ReadFile(c.PeersFile)
  297. if err != nil {
  298. return fmt.Errorf("Peers file error: %s", err)
  299. }
  300. c.Peers = ustrings.TrimSplit(string(b), ",")
  301. return nil
  302. }
  303. // DataDirFromName sets the data dir from a machine name and issue a warning
  304. // that etcd is guessing.
  305. func (c *Config) DataDirFromName() {
  306. c.DataDir = c.Name + ".etcd"
  307. log.Warnf("Using the directory %s as the etcd curation directory because a directory was not specified. ", c.DataDir)
  308. return
  309. }
  310. // NameFromHostname sets the machine name from the hostname. This is to help
  311. // people get started without thinking up a name.
  312. func (c *Config) NameFromHostname() {
  313. host, err := os.Hostname()
  314. if err != nil && host == "" {
  315. log.Fatal("Node name required and hostname not set. e.g. '-name=name'")
  316. }
  317. c.Name = host
  318. }
  319. // Reset removes all server configuration files.
  320. func (c *Config) Reset() error {
  321. if err := os.RemoveAll(filepath.Join(c.DataDir, "log")); err != nil {
  322. return err
  323. }
  324. if err := os.RemoveAll(filepath.Join(c.DataDir, "conf")); err != nil {
  325. return err
  326. }
  327. if err := os.RemoveAll(filepath.Join(c.DataDir, "snapshot")); err != nil {
  328. return err
  329. }
  330. if err := os.RemoveAll(filepath.Join(c.DataDir, "standby_info")); err != nil {
  331. return err
  332. }
  333. return nil
  334. }
  335. // Sanitize cleans the input fields.
  336. func (c *Config) Sanitize() error {
  337. var err error
  338. var url *url.URL
  339. // Sanitize the URLs first.
  340. if c.Addr, url, err = sanitizeURL(c.Addr, c.EtcdTLSInfo().Scheme()); err != nil {
  341. return fmt.Errorf("Advertised URL: %s", err)
  342. }
  343. if c.BindAddr, err = sanitizeBindAddr(c.BindAddr, url); err != nil {
  344. return fmt.Errorf("Listen Host: %s", err)
  345. }
  346. if c.Peer.Addr, url, err = sanitizeURL(c.Peer.Addr, c.PeerTLSInfo().Scheme()); err != nil {
  347. return fmt.Errorf("Peer Advertised URL: %s", err)
  348. }
  349. if c.Peer.BindAddr, err = sanitizeBindAddr(c.Peer.BindAddr, url); err != nil {
  350. return fmt.Errorf("Peer Listen Host: %s", err)
  351. }
  352. // Only guess the machine name if there is no data dir specified
  353. // because the info file should have our name
  354. if c.Name == "" && c.DataDir == "" {
  355. c.NameFromHostname()
  356. }
  357. if c.DataDir == "" && c.Name != "" && !c.ShowVersion && !c.ShowHelp {
  358. c.DataDirFromName()
  359. }
  360. return nil
  361. }
  362. // EtcdTLSInfo retrieves a TLSInfo object for the etcd server
  363. func (c *Config) EtcdTLSInfo() *server.TLSInfo {
  364. return &server.TLSInfo{
  365. CAFile: c.CAFile,
  366. CertFile: c.CertFile,
  367. KeyFile: c.KeyFile,
  368. }
  369. }
  370. // PeerRaftInfo retrieves a TLSInfo object for the peer server.
  371. func (c *Config) PeerTLSInfo() *server.TLSInfo {
  372. return &server.TLSInfo{
  373. CAFile: c.Peer.CAFile,
  374. CertFile: c.Peer.CertFile,
  375. KeyFile: c.Peer.KeyFile,
  376. }
  377. }
  378. // MetricsBucketName generates the name that should be used for a
  379. // corresponding MetricsBucket object
  380. func (c *Config) MetricsBucketName() string {
  381. return fmt.Sprintf("etcd.%s", c.Name)
  382. }
  383. // Trace determines if any trace-level information should be emitted
  384. func (c *Config) Trace() bool {
  385. return c.StrTrace == "*"
  386. }
  387. func (c *Config) ClusterConfig() *server.ClusterConfig {
  388. return &server.ClusterConfig{
  389. ActiveSize: c.Cluster.ActiveSize,
  390. RemoveDelay: c.Cluster.RemoveDelay,
  391. SyncInterval: c.Cluster.SyncInterval,
  392. }
  393. }
  394. // sanitizeURL will cleanup a host string in the format hostname[:port] and
  395. // attach a schema.
  396. func sanitizeURL(host string, defaultScheme string) (string, *url.URL, error) {
  397. // Blank URLs are fine input, just return it
  398. if len(host) == 0 {
  399. return host, &url.URL{}, nil
  400. }
  401. p, err := url.Parse(host)
  402. if err != nil {
  403. return "", nil, err
  404. }
  405. // Make sure the host is in Host:Port format
  406. _, _, err = net.SplitHostPort(host)
  407. if err != nil {
  408. return "", nil, err
  409. }
  410. p = &url.URL{Host: host, Scheme: defaultScheme}
  411. return p.String(), p, nil
  412. }
  413. // sanitizeBindAddr cleans up the BindAddr parameter and appends a port
  414. // if necessary based on the advertised port.
  415. func sanitizeBindAddr(bindAddr string, aurl *url.URL) (string, error) {
  416. // If it is a valid host:port simply return with no further checks.
  417. bhost, bport, err := net.SplitHostPort(bindAddr)
  418. if err == nil && bhost != "" {
  419. return bindAddr, nil
  420. }
  421. // SplitHostPort makes the host optional, but we don't want that.
  422. if bhost == "" && bport != "" {
  423. return "", fmt.Errorf("IP required can't use a port only")
  424. }
  425. // bindAddr doesn't have a port if we reach here so take the port from the
  426. // advertised URL.
  427. _, aport, err := net.SplitHostPort(aurl.Host)
  428. if err != nil {
  429. return "", err
  430. }
  431. return net.JoinHostPort(bindAddr, aport), nil
  432. }