config.go 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493
  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/BurntSushi/toml"
  15. )
  16. // The default location for the etcd configuration file.
  17. const DefaultSystemConfigPath = "/etc/etcd/etcd.conf"
  18. // A lookup of deprecated flags to their new flag name.
  19. var newFlagNameLookup = map[string]string{
  20. "C": "peers",
  21. "CF": "peers-file",
  22. "n": "name",
  23. "c": "addr",
  24. "cl": "bind-addr",
  25. "s": "peer-addr",
  26. "sl": "peer-bind-addr",
  27. "d": "data-dir",
  28. "m": "max-result-buffer",
  29. "r": "max-retry-attempts",
  30. "maxsize": "max-cluster-size",
  31. "clientCAFile": "ca-file",
  32. "clientCert": "cert-file",
  33. "clientKey": "key-file",
  34. "serverCAFile": "peer-ca-file",
  35. "serverCert": "peer-cert-file",
  36. "serverKey": "peer-key-file",
  37. "snapshotCount": "snapshot-count",
  38. }
  39. // Config represents the server configuration.
  40. type Config struct {
  41. SystemPath string
  42. Addr string `toml:"addr" env:"ETCD_ADDR"`
  43. BindAddr string `toml:"bind_addr" env:"ETCD_BIND_ADDR"`
  44. CAFile string `toml:"ca_file" env:"ETCD_CA_FILE"`
  45. CertFile string `toml:"cert_file" env:"ETCD_CERT_FILE"`
  46. CorsOrigins []string `toml:"cors" env:"ETCD_CORS"`
  47. DataDir string `toml:"data_dir" env:"ETCD_DATA_DIR"`
  48. Force bool
  49. KeyFile string `toml:"key_file" env:"ETCD_KEY_FILE"`
  50. Peers []string `toml:"peers" env:"ETCD_PEERS"`
  51. PeersFile string `toml:"peers_file" env:"ETCD_PEERS_FILE"`
  52. MaxClusterSize int `toml:"max_cluster_size" env:"ETCD_MAX_CLUSTER_SIZE"`
  53. MaxResultBuffer int `toml:"max_result_buffer" env:"ETCD_MAX_RESULT_BUFFER"`
  54. MaxRetryAttempts int `toml:"max_retry_attempts" env:"ETCD_MAX_RETRY_ATTEMPTS"`
  55. Name string `toml:"name" env:"ETCD_NAME"`
  56. Snapshot bool `toml:"snapshot" env:"ETCD_SNAPSHOT"`
  57. SnapshotCount int `toml:"snapshot_count" env:"ETCD_SNAPSHOTCOUNT"`
  58. Verbose bool `toml:"verbose" env:"ETCD_VERBOSE"`
  59. VeryVerbose bool `toml:"very_verbose" env:"ETCD_VERY_VERBOSE"`
  60. Peer struct {
  61. Addr string `toml:"addr" env:"ETCD_PEER_ADDR"`
  62. BindAddr string `toml:"bind_addr" env:"ETCD_PEER_BIND_ADDR"`
  63. CAFile string `toml:"ca_file" env:"ETCD_PEER_CA_FILE"`
  64. CertFile string `toml:"cert_file" env:"ETCD_PEER_CERT_FILE"`
  65. KeyFile string `toml:"key_file" env:"ETCD_PEER_KEY_FILE"`
  66. }
  67. }
  68. // NewConfig returns a Config initialized with default values.
  69. func NewConfig() *Config {
  70. c := new(Config)
  71. c.SystemPath = DefaultSystemConfigPath
  72. c.Addr = "127.0.0.1:4001"
  73. c.MaxClusterSize = 9
  74. c.MaxResultBuffer = 1024
  75. c.MaxRetryAttempts = 3
  76. c.Peer.Addr = "127.0.0.1:7001"
  77. c.SnapshotCount = 10000
  78. return c
  79. }
  80. // Loads the configuration from the system config, command line config,
  81. // environment variables, and finally command line arguments.
  82. func (c *Config) Load(arguments []string) error {
  83. var path string
  84. f := flag.NewFlagSet("etcd", -1)
  85. f.SetOutput(ioutil.Discard)
  86. f.StringVar(&path, "config", "", "path to config file")
  87. f.Parse(arguments)
  88. // Load from system file.
  89. if err := c.LoadSystemFile(); err != nil {
  90. return err
  91. }
  92. // Load from config file specified in arguments.
  93. if path != "" {
  94. if err := c.LoadFile(path); err != nil {
  95. return err
  96. }
  97. }
  98. // Load from the environment variables next.
  99. if err := c.LoadEnv(); err != nil {
  100. return err
  101. }
  102. // Load from command line flags.
  103. if err := c.LoadFlags(arguments); err != nil {
  104. return err
  105. }
  106. // Load from command line flags (deprecated).
  107. if err := c.LoadDeprecatedFlags(arguments); err != nil {
  108. if err, ok := err.(*DeprecationError); ok {
  109. fmt.Fprintln(os.Stderr, err.Error())
  110. } else {
  111. return err
  112. }
  113. }
  114. // Loads peers if a peer file was specified.
  115. if err := c.LoadPeersFile(); err != nil {
  116. return err
  117. }
  118. // Sanitize all the input fields.
  119. if err := c.Sanitize(); err != nil {
  120. return fmt.Errorf("sanitize:", err)
  121. }
  122. return nil
  123. }
  124. // Loads from the system etcd configuration file if it exists.
  125. func (c *Config) LoadSystemFile() error {
  126. if _, err := os.Stat(c.SystemPath); os.IsNotExist(err) {
  127. return nil
  128. }
  129. return c.LoadFile(c.SystemPath)
  130. }
  131. // Loads configuration from a file.
  132. func (c *Config) LoadFile(path string) error {
  133. _, err := toml.DecodeFile(path, &c)
  134. return err
  135. }
  136. // LoadEnv loads the configuration via environment variables.
  137. func (c *Config) LoadEnv() error {
  138. if err := c.loadEnv(c); err != nil {
  139. return err
  140. }
  141. if err := c.loadEnv(&c.Peer); err != nil {
  142. return err
  143. }
  144. return nil
  145. }
  146. func (c *Config) loadEnv(target interface{}) error {
  147. value := reflect.Indirect(reflect.ValueOf(target))
  148. typ := value.Type()
  149. for i := 0; i < typ.NumField(); i++ {
  150. field := typ.Field(i)
  151. // Retrieve environment variable.
  152. v := strings.TrimSpace(os.Getenv(field.Tag.Get("env")))
  153. if v == "" {
  154. continue
  155. }
  156. // Set the appropriate type.
  157. switch field.Type.Kind() {
  158. case reflect.Bool:
  159. value.Field(i).SetBool(v != "0" && v != "false")
  160. case reflect.Int:
  161. newValue, err := strconv.ParseInt(v, 10, 0)
  162. if err != nil {
  163. return fmt.Errorf("Parse error: %s: %s", field.Tag.Get("env"), err)
  164. }
  165. value.Field(i).SetInt(newValue)
  166. case reflect.String:
  167. value.Field(i).SetString(v)
  168. case reflect.Slice:
  169. value.Field(i).Set(reflect.ValueOf(trimsplit(v, ",")))
  170. }
  171. }
  172. return nil
  173. }
  174. // Loads deprecated configuration settings from the command line.
  175. func (c *Config) LoadDeprecatedFlags(arguments []string) error {
  176. var peers string
  177. f := flag.NewFlagSet(os.Args[0], flag.ContinueOnError)
  178. f.SetOutput(ioutil.Discard)
  179. f.StringVar(&peers, "C", "", "(deprecated)")
  180. f.StringVar(&c.PeersFile, "CF", c.PeersFile, "(deprecated)")
  181. f.StringVar(&c.Name, "n", c.Name, "(deprecated)")
  182. f.StringVar(&c.Addr, "c", c.Addr, "(deprecated)")
  183. f.StringVar(&c.BindAddr, "cl", c.BindAddr, "the listening hostname for etcd client communication (defaults to advertised ip)")
  184. f.StringVar(&c.Peer.Addr, "s", c.Peer.Addr, "the advertised public hostname:port for raft server communication")
  185. f.StringVar(&c.Peer.BindAddr, "sl", c.Peer.BindAddr, "the listening hostname for raft server communication (defaults to advertised ip)")
  186. f.StringVar(&c.Peer.CAFile, "serverCAFile", c.Peer.CAFile, "the path of the CAFile")
  187. f.StringVar(&c.Peer.CertFile, "serverCert", c.Peer.CertFile, "the cert file of the server")
  188. f.StringVar(&c.Peer.KeyFile, "serverKey", c.Peer.KeyFile, "the key file of the server")
  189. f.StringVar(&c.CAFile, "clientCAFile", c.CAFile, "the path of the client CAFile")
  190. f.StringVar(&c.CertFile, "clientCert", c.CertFile, "the cert file of the client")
  191. f.StringVar(&c.KeyFile, "clientKey", c.KeyFile, "the key file of the client")
  192. f.StringVar(&c.DataDir, "d", c.DataDir, "the directory to store log and snapshot")
  193. f.IntVar(&c.MaxResultBuffer, "m", c.MaxResultBuffer, "the max size of result buffer")
  194. f.IntVar(&c.MaxRetryAttempts, "r", c.MaxRetryAttempts, "the max retry attempts when trying to join a cluster")
  195. f.IntVar(&c.MaxClusterSize, "maxsize", c.MaxClusterSize, "the max size of the cluster")
  196. f.IntVar(&c.SnapshotCount, "snapshotCount", c.SnapshotCount, "save the in-memory logs and states to a snapshot file a given number of transactions")
  197. f.Parse(arguments)
  198. // Convert some parameters to lists.
  199. if peers != "" {
  200. c.Peers = trimsplit(peers, ",")
  201. }
  202. // Generate deprecation warning.
  203. warnings := make([]string, 0)
  204. f.Visit(func(f *flag.Flag) {
  205. warnings = append(warnings, fmt.Sprintf("[deprecated] use -%s, not -%s", newFlagNameLookup[f.Name], f.Name))
  206. })
  207. if len(warnings) > 0 {
  208. return &DeprecationError{strings.Join(warnings, "\n")}
  209. }
  210. return nil
  211. }
  212. // Loads configuration from command line flags.
  213. func (c *Config) LoadFlags(arguments []string) error {
  214. var peers, cors string
  215. f := flag.NewFlagSet(os.Args[0], flag.ContinueOnError)
  216. f.SetOutput(ioutil.Discard)
  217. f.BoolVar(&c.Force, "f", false, "force new node configuration if existing is found (WARNING: data loss!)")
  218. f.BoolVar(&c.Force, "force", false, "force new node configuration if existing is found (WARNING: data loss!)")
  219. f.BoolVar(&c.Verbose, "v", c.Verbose, "verbose logging")
  220. f.BoolVar(&c.VeryVerbose, "vv", c.Verbose, "very verbose logging")
  221. f.StringVar(&peers, "peers", "", "the ip address and port of a existing peers in the cluster, sepearate by comma")
  222. f.StringVar(&c.PeersFile, "peers-file", c.PeersFile, "the file contains a list of existing peers in the cluster, seperate by comma")
  223. f.StringVar(&c.Name, "name", c.Name, "the node name (required)")
  224. f.StringVar(&c.Addr, "addr", c.Addr, "the advertised public hostname:port for etcd client communication")
  225. f.StringVar(&c.BindAddr, "bind-addr", c.BindAddr, "the listening hostname for etcd client communication (defaults to advertised ip)")
  226. f.StringVar(&c.Peer.Addr, "peer-addr", c.Peer.Addr, "the advertised public hostname:port for raft server communication")
  227. f.StringVar(&c.Peer.BindAddr, "peer-bind-addr", c.Peer.BindAddr, "the listening hostname for raft server communication (defaults to advertised ip)")
  228. f.StringVar(&c.Peer.CAFile, "peer-ca-file", c.Peer.CAFile, "the path of the CAFile")
  229. f.StringVar(&c.Peer.CertFile, "peer-cert-file", c.Peer.CertFile, "the cert file of the server")
  230. f.StringVar(&c.Peer.KeyFile, "peer-key-file", c.Peer.KeyFile, "the key file of the server")
  231. f.StringVar(&c.CAFile, "ca-file", c.CAFile, "the path of the client CAFile")
  232. f.StringVar(&c.CertFile, "cert-file", c.CertFile, "the cert file of the client")
  233. f.StringVar(&c.KeyFile, "key-file", c.KeyFile, "the key file of the client")
  234. f.StringVar(&c.DataDir, "data-dir", c.DataDir, "the directory to store log and snapshot")
  235. f.IntVar(&c.MaxResultBuffer, "max-result-buffer", c.MaxResultBuffer, "the max size of result buffer")
  236. f.IntVar(&c.MaxRetryAttempts, "max-retry-attempts", c.MaxRetryAttempts, "the max retry attempts when trying to join a cluster")
  237. f.IntVar(&c.MaxClusterSize, "max-cluster-size", c.MaxClusterSize, "the max size of the cluster")
  238. f.StringVar(&cors, "cors", "", "whitelist origins for cross-origin resource sharing (e.g. '*' or 'http://localhost:8001,etc')")
  239. f.BoolVar(&c.Snapshot, "snapshot", c.Snapshot, "open or close snapshot")
  240. f.IntVar(&c.SnapshotCount, "snapshot-count", c.SnapshotCount, "save the in-memory logs and states to a snapshot file a given number of transactions")
  241. // These flags are ignored since they were already parsed.
  242. var path string
  243. f.StringVar(&path, "config", "", "path to config file")
  244. f.Parse(arguments)
  245. // Convert some parameters to lists.
  246. if peers != "" {
  247. c.Peers = trimsplit(peers, ",")
  248. }
  249. if cors != "" {
  250. c.CorsOrigins = trimsplit(cors, ",")
  251. }
  252. // Force remove server configuration if specified.
  253. if c.Force {
  254. c.Reset()
  255. }
  256. return nil
  257. }
  258. // LoadPeersFile loads the peers listed in the peers file.
  259. func (c *Config) LoadPeersFile() error {
  260. if c.PeersFile == "" {
  261. return nil
  262. }
  263. b, err := ioutil.ReadFile(c.PeersFile)
  264. if err != nil {
  265. return fmt.Errorf("Peers file error: %s", err)
  266. }
  267. c.Peers = trimsplit(string(b), ",")
  268. return nil
  269. }
  270. // Reset removes all server configuration files.
  271. func (c *Config) Reset() error {
  272. if err := os.RemoveAll(filepath.Join(c.DataDir, "info")); err != nil {
  273. return err
  274. }
  275. if err := os.RemoveAll(filepath.Join(c.DataDir, "log")); err != nil {
  276. return err
  277. }
  278. if err := os.RemoveAll(filepath.Join(c.DataDir, "conf")); err != nil {
  279. return err
  280. }
  281. if err := os.RemoveAll(filepath.Join(c.DataDir, "snapshot")); err != nil {
  282. return err
  283. }
  284. return nil
  285. }
  286. // Reads the info file from the file system or initializes it based on the config.
  287. func (c *Config) Info() (*Info, error) {
  288. info := &Info{}
  289. path := filepath.Join(c.DataDir, "info")
  290. // Open info file and read it out.
  291. f, err := os.Open(path)
  292. if err != nil && !os.IsNotExist(err) {
  293. return nil, err
  294. } else if f != nil {
  295. defer f.Close()
  296. if err := json.NewDecoder(f).Decode(&info); err != nil {
  297. return nil, err
  298. }
  299. return info, nil
  300. }
  301. // If the file doesn't exist then initialize it.
  302. info.Name = strings.TrimSpace(c.Name)
  303. info.EtcdURL = c.Addr
  304. info.EtcdListenHost = c.BindAddr
  305. info.RaftURL = c.Peer.Addr
  306. info.RaftListenHost = c.Peer.BindAddr
  307. info.EtcdTLS = c.TLSInfo()
  308. info.RaftTLS = c.PeerTLSInfo()
  309. // Write to file.
  310. f, err = os.Create(path)
  311. if err != nil {
  312. return nil, err
  313. }
  314. defer f.Close()
  315. if err := json.NewEncoder(f).Encode(info); err != nil {
  316. return nil, err
  317. }
  318. return info, nil
  319. }
  320. // Sanitize cleans the input fields.
  321. func (c *Config) Sanitize() error {
  322. tlsConfig, err := c.TLSConfig()
  323. if err != nil {
  324. return err
  325. }
  326. peerTlsConfig, err := c.PeerTLSConfig()
  327. if err != nil {
  328. return err
  329. }
  330. // Sanitize the URLs first.
  331. if c.Addr, err = sanitizeURL(c.Addr, tlsConfig.Scheme); err != nil {
  332. return fmt.Errorf("Advertised URL: %s", err)
  333. }
  334. if c.BindAddr, err = sanitizeBindAddr(c.BindAddr, c.Addr); err != nil {
  335. return fmt.Errorf("Listen Host: %s", err)
  336. }
  337. if c.Peer.Addr, err = sanitizeURL(c.Peer.Addr, peerTlsConfig.Scheme); err != nil {
  338. return fmt.Errorf("Peer Advertised URL: %s", err)
  339. }
  340. if c.Peer.BindAddr, err = sanitizeBindAddr(c.Peer.BindAddr, c.Peer.Addr); err != nil {
  341. return fmt.Errorf("Peer Listen Host: %s", err)
  342. }
  343. return nil
  344. }
  345. // TLSInfo retrieves a TLSInfo object for the client server.
  346. func (c *Config) TLSInfo() TLSInfo {
  347. return TLSInfo{
  348. CAFile: c.CAFile,
  349. CertFile: c.CertFile,
  350. KeyFile: c.KeyFile,
  351. }
  352. }
  353. // ClientTLSConfig generates the TLS configuration for the client server.
  354. func (c *Config) TLSConfig() (TLSConfig, error) {
  355. return c.TLSInfo().Config()
  356. }
  357. // PeerTLSInfo retrieves a TLSInfo object for the peer server.
  358. func (c *Config) PeerTLSInfo() TLSInfo {
  359. return TLSInfo{
  360. CAFile: c.Peer.CAFile,
  361. CertFile: c.Peer.CertFile,
  362. KeyFile: c.Peer.KeyFile,
  363. }
  364. }
  365. // PeerTLSConfig generates the TLS configuration for the peer server.
  366. func (c *Config) PeerTLSConfig() (TLSConfig, error) {
  367. return c.PeerTLSInfo().Config()
  368. }
  369. // sanitizeURL will cleanup a host string in the format hostname:port and
  370. // attach a schema.
  371. func sanitizeURL(host string, defaultScheme string) (string, error) {
  372. // Blank URLs are fine input, just return it
  373. if len(host) == 0 {
  374. return host, nil
  375. }
  376. p, err := url.Parse(host)
  377. if err != nil {
  378. return "", err
  379. }
  380. // Make sure the host is in Host:Port format
  381. _, _, err = net.SplitHostPort(host)
  382. if err != nil {
  383. return "", err
  384. }
  385. p = &url.URL{Host: host, Scheme: defaultScheme}
  386. return p.String(), nil
  387. }
  388. // sanitizeBindAddr cleans up the BindAddr parameter and appends a port
  389. // if necessary based on the advertised port.
  390. func sanitizeBindAddr(bindAddr string, addr string) (string, error) {
  391. aurl, err := url.Parse(addr)
  392. if err != nil {
  393. return "", err
  394. }
  395. ahost, aport, err := net.SplitHostPort(aurl.Host)
  396. if err != nil {
  397. return "", err
  398. }
  399. // If the listen host isn't set use the advertised host
  400. if bindAddr == "" {
  401. bindAddr = ahost
  402. }
  403. return net.JoinHostPort(bindAddr, aport), nil
  404. }
  405. // DeprecationError is a warning for CLI users that one or more arguments will
  406. // not be supported in future released.
  407. type DeprecationError struct {
  408. s string
  409. }
  410. func (e *DeprecationError) Error() string {
  411. return e.s
  412. }