config.go 13 KB

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