config.go 15 KB

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