config.go 12 KB

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