Compare commits
75 Commits
Author | SHA1 | Date | |
---|---|---|---|
9fa3bea5a2 | |||
2fc8304300 | |||
087ba30a90 | |||
e1df265dc5 | |||
8059598332 | |||
e1e2daa205 | |||
399931cec9 | |||
49715173cb | |||
ad4f231b40 | |||
55263bc6b5 | |||
262d769168 | |||
2f6ea0a0e5 | |||
fc8020b7d6 | |||
03a99cf9b1 | |||
eae1e18500 | |||
6666b20d91 | |||
2d4592e8c5 | |||
12fec1f936 | |||
d6523fe463 | |||
c25127a699 | |||
9f031e6218 | |||
e55724e959 | |||
29af192e3d | |||
2fc79912c2 | |||
ebb8d781b5 | |||
2e30b3c17f | |||
9a2d82854e | |||
b077dcf6c4 | |||
2b572cb6e8 | |||
f36d55f062 | |||
9f70568a02 | |||
1ca7d1e064 | |||
4f1f003d04 | |||
49e0dff2b8 | |||
686837227e | |||
f2652f005e | |||
5490eb5406 | |||
70dda950ed | |||
a884f2a18a | |||
bdeb96be0f | |||
c00594e680 | |||
919cd380ec | |||
b83aec6b87 | |||
05bfb369ef | |||
0639c4c86d | |||
877b3d51bb | |||
d9df58beb8 | |||
1cffdb3a48 | |||
0593a52107 | |||
f7854c4ab9 | |||
13b0e72304 | |||
43791a2f41 | |||
a8d966d8f3 | |||
ad7194d5d0 | |||
084dcb5596 | |||
8a0266a806 | |||
2338481bb1 | |||
ce1e19ae2f | |||
a288333e6f | |||
097aac79f5 | |||
cd820269a6 | |||
ac7e6bb002 | |||
4b45cd4110 | |||
fb426aec9e | |||
774cb03f83 | |||
20147c5357 | |||
973bde9a07 | |||
774cf34827 | |||
92df44276d | |||
f4f429d4e3 | |||
e01a1f70c3 | |||
2e4ea503b0 | |||
c7aef5fdf2 | |||
c4605160c5 | |||
054de85da2 |
13
CHANGELOG
13
CHANGELOG
@ -1,3 +1,16 @@
|
||||
v0.4.6
|
||||
* Fix long-term timer leak (#900, #875, #868, #904)
|
||||
* Fix `Running` field in standby_info file (#881)
|
||||
* Add `quorum=true` query parameter for GET requests (#866, #883)
|
||||
* Add `Access-Control-Allow-Headers` header for CORS requests (#886)
|
||||
* Various documentation improvements (#907, #882)
|
||||
|
||||
v0.4.5
|
||||
* Flush headers immediatly on `wait=true` requests (#877)
|
||||
* Add `ETCD_HTTP_READ_TIMEOUT` and `ETCD_HTTP_WRITE_TIMEOUT` (#880)
|
||||
* Add `ETCDCTL_PEERS` configuration to etcdctl (#95)
|
||||
* etcdctl takes stdin for mk (#91)
|
||||
|
||||
v0.4.4
|
||||
* Fix `--no-sync` flag in etcdctl (#83)
|
||||
* Improved logging for machine removal (#844)
|
||||
|
@ -30,47 +30,32 @@ The coding style suggested by the Golang community is used in etcd. See [style d
|
||||
|
||||
Please follow this style to make etcd easy to review, maintain and develop.
|
||||
|
||||
### Format of the commit message
|
||||
### Format of the Commit Message
|
||||
|
||||
etcd follows a rough convention for commit messages borrowed from Angularjs. This is an example of a commit:
|
||||
We follow a rough convention for commit messages that is designed to answer two
|
||||
questions: what changed and why. The subject line should feature the what and
|
||||
the body of the commit should describe the why.
|
||||
|
||||
```
|
||||
feat(scripts/test-cluster): add a cluster test command
|
||||
scripts: add the test-cluster command
|
||||
|
||||
this uses tmux to setup a test cluster that you can easily kill and
|
||||
start for debugging.
|
||||
this uses tmux to setup a test cluster that you can easily kill and
|
||||
start for debugging.
|
||||
|
||||
Fixes #38
|
||||
```
|
||||
|
||||
The format can be more formally described as follows:
|
||||
The format can be described more formally as follows:
|
||||
|
||||
```
|
||||
<type>(<scope>): <subject>
|
||||
<subsystem>: <what changed>
|
||||
<BLANK LINE>
|
||||
<body>
|
||||
<why this change was made>
|
||||
<BLANK LINE>
|
||||
<footer>
|
||||
```
|
||||
|
||||
The first line is the subject and should be no longer than 70 characters, the second line is always blank, and other lines should be wrapped at 80 characters. This allows the message to be easier to read on github as well as
|
||||
in various git tools.
|
||||
|
||||
### Subject line
|
||||
|
||||
The subject line contains a succinct description of the change.
|
||||
|
||||
### Allowed <type>s
|
||||
- feat (feature)
|
||||
- fix (bug fix)
|
||||
- docs (documentation)
|
||||
- style (formatting, missing semi colons, …)
|
||||
- refactor
|
||||
- test (when adding missing tests)
|
||||
- chore (maintain)
|
||||
|
||||
### Allowed <scope>s
|
||||
|
||||
Scopes can be anything specifying the place of the commit change within the repository. For example, "store", "API", etc.
|
||||
|
||||
### More details on commits
|
||||
|
||||
For more details see the [angularjs commit style guide](https://docs.google.com/a/coreos.com/document/d/1QrDFcIiPjSLDn3EL15IJygNPiHORgU1_OOAqWjiDU5Y/edit#).
|
||||
The first line is the subject and should be no longer than 70 characters, the
|
||||
second line is always blank, and other lines should be wrapped at 80 characters.
|
||||
This allows the message to be easier to read on GitHub as well as in various
|
||||
git tools.
|
||||
|
@ -13,6 +13,14 @@ This will bring up etcd listening on default ports (4001 for client communicatio
|
||||
The `-data-dir machine0` argument tells etcd to write machine configuration, logs and snapshots to the `./machine0/` directory.
|
||||
The `-name machine0` tells the rest of the cluster that this machine is named machine0.
|
||||
|
||||
## Getting the etcd version
|
||||
|
||||
The etcd version of a specific instance can be obtained from the `/version` endpoint.
|
||||
|
||||
```sh
|
||||
curl -L http://127.0.0.1:4001/version
|
||||
```
|
||||
|
||||
## Key Space Operations
|
||||
|
||||
The primary API of etcd is a hierarchical key space.
|
||||
@ -833,6 +841,8 @@ curl -L http://127.0.0.1:4001/v2/keys/afile -XPUT --data-urlencode value@afile.t
|
||||
|
||||
### Read Consistency
|
||||
|
||||
#### Read from the Master
|
||||
|
||||
Followers in a cluster can be behind the leader in their copy of the keyspace.
|
||||
If your application wants or needs the most up-to-date version of a key then it should ensure it reads from the current leader.
|
||||
By using the `consistent=true` flag in your GET requests, etcd will make sure you are talking to the current master.
|
||||
@ -843,6 +853,19 @@ The client is told the write was successful and the keyspace is updated.
|
||||
Meanwhile F2 has partitioned from the network and will have an out-of-date version of the keyspace until the partition resolves.
|
||||
Since F2 missed the most recent write, a client reading from F2 will have an out-of-date version of the keyspace.
|
||||
|
||||
Implementation notes on `consistent=true`: If the leader you are talking to is
|
||||
partitioned it will be unable to determine if it is not currently the master.
|
||||
In a later version we will provide a mechanism to set an upperbound of time
|
||||
that the current master can be unable to contact the quorom and still serve
|
||||
reads.
|
||||
|
||||
### Read Linearization
|
||||
|
||||
If you want a read that is fully linearized you can use a `quorum=true` GET.
|
||||
The read will take a very similar path to a write and will have a similar
|
||||
speed. If you are unsure if you need this feature feel free to email etcd-dev
|
||||
for advice.
|
||||
|
||||
## Lock Module (*Deprecated and Removed*)
|
||||
|
||||
The lock module is used to serialize access to resources used by clients.
|
||||
|
@ -26,13 +26,15 @@ The full documentation is contained in the [API docs](https://github.com/coreos/
|
||||
|
||||
### Required
|
||||
|
||||
* `-name` - The node name. Defaults to the hostname.
|
||||
* `-name` - The node name. Defaults to a UUID.
|
||||
|
||||
### Optional
|
||||
|
||||
* `-addr` - The advertised public hostname:port for client communication. Defaults to `127.0.0.1:4001`.
|
||||
* `-discovery` - A URL to use for discovering the peer list. (i.e `"https://discovery.etcd.io/your-unique-key"`).
|
||||
* `-bind-addr` - The listening hostname for client communication. Defaults to advertised IP.
|
||||
* `-http-read-timeout` - The number of seconds before an HTTP read operation is timed out.
|
||||
* `-http-write-timeout` - The number of seconds before an HTTP write operation is timed out.
|
||||
* `-bind-addr` - The listening hostname for client communication. Defaults to 0.0.0.0 and the advertised port.
|
||||
* `-peers` - A comma separated list of peers in the cluster (i.e `"203.0.113.101:7001,203.0.113.102:7001"`).
|
||||
* `-peers-file` - The file path containing a comma separated list of peers in the cluster.
|
||||
* `-ca-file` - The path of the client CAFile. Enables client cert authentication when present.
|
||||
@ -43,10 +45,9 @@ The full documentation is contained in the [API docs](https://github.com/coreos/
|
||||
* `-cpuprofile` - The path to a file to output CPU profile data. Enables CPU profiling when present.
|
||||
* `-data-dir` - The directory to store log and snapshot. Defaults to the current working directory.
|
||||
* `-max-result-buffer` - The max size of result buffer. Defaults to `1024`.
|
||||
* `-max-cluster-size` - The max size of the cluster. Defaults to `9`.
|
||||
* `-max-retry-attempts` - The max retry attempts when trying to join a cluster. Defaults to `3`.
|
||||
* `-peer-addr` - The advertised public hostname:port for server communication. Defaults to `127.0.0.1:7001`.
|
||||
* `-peer-bind-addr` - The listening hostname for server communication. Defaults to advertised IP.
|
||||
* `-peer-bind-addr` - The listening hostname for server communication. Defaults to 0.0.0.0 and the advertised peer port.
|
||||
* `-peer-ca-file` - The path of the CAFile. Enables client/peer cert authentication when present.
|
||||
* `-peer-cert-file` - The cert file of the server.
|
||||
* `-peer-key-file` - The key file of the server.
|
||||
@ -54,8 +55,8 @@ The full documentation is contained in the [API docs](https://github.com/coreos/
|
||||
* `-peer-heartbeat-interval` - The number of milliseconds in between heartbeat requests
|
||||
* `-snapshot=false` - Disable log snapshots. Defaults to `true`.
|
||||
* `-cluster-active-size` - The expected number of instances participating in the consensus protocol. Only applied if the etcd instance is the first peer in the cluster.
|
||||
* `-cluster-remove-delay` - The delay before one node is removed from the cluster since it cannot be connected at all. Only applied if the etcd instance is the first peer in the cluster.
|
||||
* `-cluster-sync-interval` - The interval between synchronization for standby-mode instance with the cluster. Only applied if the etcd instance is the first peer in the cluster.
|
||||
* `-cluster-remove-delay` - The number of seconds before one node is removed from the cluster since it cannot be connected at all. Only applied if the etcd instance is the first peer in the cluster.
|
||||
* `-cluster-sync-interval` - The number of seconds between synchronization for standby-mode instance with the cluster. Only applied if the etcd instance is the first peer in the cluster.
|
||||
* `-v` - Enable verbose logging. Defaults to `false`.
|
||||
* `-vv` - Enable very verbose logging. Defaults to `false`.
|
||||
* `-version` - Print the version and exit.
|
||||
@ -74,6 +75,8 @@ cors = []
|
||||
cpu_profile_file = ""
|
||||
data_dir = "."
|
||||
discovery = "http://etcd.local:4001/v2/keys/_etcd/registry/examplecluster"
|
||||
http_read_timeout = 10
|
||||
http_write_timeout = 10
|
||||
key_file = ""
|
||||
peers = []
|
||||
peers_file = ""
|
||||
@ -109,6 +112,8 @@ sync_interval = 5.0
|
||||
* `ETCD_CPU_PROFILE_FILE`
|
||||
* `ETCD_DATA_DIR`
|
||||
* `ETCD_DISCOVERY`
|
||||
* `ETCD_CLUSTER_HTTP_READ_TIMEOUT`
|
||||
* `ETCD_CLUSTER_HTTP_WRITE_TIMEOUT`
|
||||
* `ETCD_KEY_FILE`
|
||||
* `ETCD_PEERS`
|
||||
* `ETCD_PEERS_FILE`
|
||||
|
@ -17,7 +17,6 @@
|
||||
|
||||
**Python libraries**
|
||||
|
||||
- [transitorykris/etcd-py](https://github.com/transitorykris/etcd-py)
|
||||
- [jplana/python-etcd](https://github.com/jplana/python-etcd) - Supports v2
|
||||
- [russellhaering/txetcd](https://github.com/russellhaering/txetcd) - a Twisted Python library
|
||||
- [cholcombe973/autodock](https://github.com/cholcombe973/autodock) - A docker deployment automation tool
|
||||
|
@ -28,11 +28,11 @@ The other important cluster optimization is to always have an odd active cluster
|
||||
|--------------|------------|-------------------|
|
||||
| 1 peers | 1 peers | None |
|
||||
| 3 peers | 2 peers | 1 peer |
|
||||
| 4 peers | 3 peers | 2 peers |
|
||||
| 5 peers | 3 peers | **3 peers** |
|
||||
| 4 peers | 3 peers | 1 peer |
|
||||
| 5 peers | 3 peers | **2 peers** |
|
||||
| 6 peers | 4 peers | 2 peers |
|
||||
| 7 peers | 4 peers | **3 peers** |
|
||||
| 8 peers | 5 peers | 3 peers |
|
||||
| 9 peers | 5 peers | **4 peers** |
|
||||
|
||||
As you can see, adding another peer to bring the number of active peers up to an odd size is always worth it. During a network partition, an odd number of active peers also guarantees that there will almost always be a majority of the cluster that can continue to operate and be the source of truth when the partition ends.
|
||||
As you can see, adding another peer to bring the number of active peers up to an odd size is always worth it. During a network partition, an odd number of active peers also guarantees that there will almost always be a majority of the cluster that can continue to operate and be the source of truth when the partition ends.
|
||||
|
5
NOTICE
Normal file
5
NOTICE
Normal file
@ -0,0 +1,5 @@
|
||||
CoreOS Project
|
||||
Copyright 2014 CoreOS, Inc
|
||||
|
||||
This product includes software developed at CoreOS, Inc.
|
||||
(http://www.coreos.com/).
|
@ -1,6 +1,6 @@
|
||||
# etcd
|
||||
|
||||
README version 0.4.4
|
||||
README version 0.4.8
|
||||
|
||||
A highly-available key value store for shared configuration and service discovery.
|
||||
etcd is inspired by [Apache ZooKeeper][zookeeper] and [doozer][doozer], with a focus on being:
|
||||
|
@ -36,7 +36,7 @@ var newFlagNameLookup = map[string]string{
|
||||
"d": "data-dir",
|
||||
"m": "max-result-buffer",
|
||||
"r": "max-retry-attempts",
|
||||
"maxsize": "max-cluster-size",
|
||||
"maxsize": "cluster-active-size",
|
||||
"clientCAFile": "ca-file",
|
||||
"clientCert": "cert-file",
|
||||
"clientKey": "key-file",
|
||||
@ -45,6 +45,7 @@ var newFlagNameLookup = map[string]string{
|
||||
"serverKey": "peer-key-file",
|
||||
"snapshotCount": "snapshot-count",
|
||||
"peer-heartbeat-timeout": "peer-heartbeat-interval",
|
||||
"max-cluster-size": "cluster-active-size",
|
||||
}
|
||||
|
||||
// Config represents the server configuration.
|
||||
@ -61,6 +62,8 @@ type Config struct {
|
||||
Discovery string `toml:"discovery" env:"ETCD_DISCOVERY"`
|
||||
Force bool
|
||||
KeyFile string `toml:"key_file" env:"ETCD_KEY_FILE"`
|
||||
HTTPReadTimeout float64 `toml:"http_read_timeout" env:"ETCD_HTTP_READ_TIMEOUT"`
|
||||
HTTPWriteTimeout float64 `toml:"http_write_timeout" env:"ETCD_HTTP_WRITE_TIMEOUT"`
|
||||
Peers []string `toml:"peers" env:"ETCD_PEERS"`
|
||||
PeersFile string `toml:"peers_file" env:"ETCD_PEERS_FILE"`
|
||||
MaxResultBuffer int `toml:"max_result_buffer" env:"ETCD_MAX_RESULT_BUFFER"`
|
||||
@ -83,7 +86,7 @@ type Config struct {
|
||||
HeartbeatInterval int `toml:"heartbeat_interval" env:"ETCD_PEER_HEARTBEAT_INTERVAL"`
|
||||
ElectionTimeout int `toml:"election_timeout" env:"ETCD_PEER_ELECTION_TIMEOUT"`
|
||||
}
|
||||
strTrace string `toml:"trace" env:"ETCD_TRACE"`
|
||||
StrTrace string `toml:"trace" env:"ETCD_TRACE"`
|
||||
GraphiteHost string `toml:"graphite_host" env:"ETCD_GRAPHITE_HOST"`
|
||||
Cluster struct {
|
||||
ActiveSize int `toml:"active_size" env:"ETCD_CLUSTER_ACTIVE_SIZE"`
|
||||
@ -97,6 +100,8 @@ func New() *Config {
|
||||
c := new(Config)
|
||||
c.SystemPath = DefaultSystemConfigPath
|
||||
c.Addr = "127.0.0.1:4001"
|
||||
c.HTTPReadTimeout = server.DefaultReadTimeout
|
||||
c.HTTPWriteTimeout = server.DefaultWriteTimeout
|
||||
c.MaxResultBuffer = 1024
|
||||
c.MaxRetryAttempts = 3
|
||||
c.RetryInterval = 10.0
|
||||
@ -254,6 +259,9 @@ func (c *Config) LoadFlags(arguments []string) error {
|
||||
f.StringVar(&c.Peer.CertFile, "peer-cert-file", c.Peer.CertFile, "")
|
||||
f.StringVar(&c.Peer.KeyFile, "peer-key-file", c.Peer.KeyFile, "")
|
||||
|
||||
f.Float64Var(&c.HTTPReadTimeout, "http-read-timeout", c.HTTPReadTimeout, "")
|
||||
f.Float64Var(&c.HTTPWriteTimeout, "http-write-timeout", c.HTTPReadTimeout, "")
|
||||
|
||||
f.StringVar(&c.DataDir, "data-dir", c.DataDir, "")
|
||||
f.IntVar(&c.MaxResultBuffer, "max-result-buffer", c.MaxResultBuffer, "")
|
||||
f.IntVar(&c.MaxRetryAttempts, "max-retry-attempts", c.MaxRetryAttempts, "")
|
||||
@ -267,7 +275,7 @@ func (c *Config) LoadFlags(arguments []string) error {
|
||||
f.IntVar(&c.SnapshotCount, "snapshot-count", c.SnapshotCount, "")
|
||||
f.StringVar(&c.CPUProfileFile, "cpuprofile", "", "")
|
||||
|
||||
f.StringVar(&c.strTrace, "trace", "", "")
|
||||
f.StringVar(&c.StrTrace, "trace", "", "")
|
||||
f.StringVar(&c.GraphiteHost, "graphite-host", "", "")
|
||||
|
||||
f.IntVar(&c.Cluster.ActiveSize, "cluster-active-size", c.Cluster.ActiveSize, "")
|
||||
@ -297,6 +305,8 @@ func (c *Config) LoadFlags(arguments []string) error {
|
||||
f.IntVar(&c.MaxRetryAttempts, "r", c.MaxRetryAttempts, "(deprecated)")
|
||||
f.IntVar(&c.SnapshotCount, "snapshotCount", c.SnapshotCount, "(deprecated)")
|
||||
f.IntVar(&c.Peer.HeartbeatInterval, "peer-heartbeat-timeout", c.Peer.HeartbeatInterval, "(deprecated)")
|
||||
f.IntVar(&c.Cluster.ActiveSize, "max-cluster-size", c.Cluster.ActiveSize, "(deprecated)")
|
||||
f.IntVar(&c.Cluster.ActiveSize, "maxsize", c.Cluster.ActiveSize, "(deprecated)")
|
||||
// END DEPRECATED FLAGS
|
||||
|
||||
if err := f.Parse(arguments); err != nil {
|
||||
@ -431,7 +441,7 @@ func (c *Config) MetricsBucketName() string {
|
||||
|
||||
// Trace determines if any trace-level information should be emitted
|
||||
func (c *Config) Trace() bool {
|
||||
return c.strTrace == "*"
|
||||
return c.StrTrace == "*"
|
||||
}
|
||||
|
||||
func (c *Config) ClusterConfig() *server.ClusterConfig {
|
||||
|
@ -27,9 +27,11 @@ func TestConfigTOML(t *testing.T) {
|
||||
max_result_buffer = 512
|
||||
max_retry_attempts = 5
|
||||
name = "test-name"
|
||||
http_read_timeout = 2.34
|
||||
snapshot = true
|
||||
verbose = true
|
||||
very_verbose = true
|
||||
http_write_timeout = 1.23
|
||||
|
||||
[peer]
|
||||
addr = "127.0.0.1:7002"
|
||||
@ -52,6 +54,8 @@ func TestConfigTOML(t *testing.T) {
|
||||
assert.Equal(t, c.CorsOrigins, []string{"*"}, "")
|
||||
assert.Equal(t, c.DataDir, "/tmp/data", "")
|
||||
assert.Equal(t, c.Discovery, "http://example.com/foobar", "")
|
||||
assert.Equal(t, c.HTTPReadTimeout, 2.34, "")
|
||||
assert.Equal(t, c.HTTPWriteTimeout, 1.23, "")
|
||||
assert.Equal(t, c.KeyFile, "/tmp/file.key", "")
|
||||
assert.Equal(t, c.BindAddr, "127.0.0.1:4003", "")
|
||||
assert.Equal(t, c.Peers, []string{"coreos.com:4001", "coreos.com:4002"}, "")
|
||||
@ -80,6 +84,8 @@ func TestConfigEnv(t *testing.T) {
|
||||
os.Setenv("ETCD_CORS", "localhost:4001,localhost:4002")
|
||||
os.Setenv("ETCD_DATA_DIR", "/tmp/data")
|
||||
os.Setenv("ETCD_DISCOVERY", "http://example.com/foobar")
|
||||
os.Setenv("ETCD_HTTP_READ_TIMEOUT", "2.34")
|
||||
os.Setenv("ETCD_HTTP_WRITE_TIMEOUT", "1.23")
|
||||
os.Setenv("ETCD_KEY_FILE", "/tmp/file.key")
|
||||
os.Setenv("ETCD_BIND_ADDR", "127.0.0.1:4003")
|
||||
os.Setenv("ETCD_PEERS", "coreos.com:4001,coreos.com:4002")
|
||||
@ -107,6 +113,8 @@ func TestConfigEnv(t *testing.T) {
|
||||
assert.Equal(t, c.CorsOrigins, []string{"localhost:4001", "localhost:4002"}, "")
|
||||
assert.Equal(t, c.DataDir, "/tmp/data", "")
|
||||
assert.Equal(t, c.Discovery, "http://example.com/foobar", "")
|
||||
assert.Equal(t, c.HTTPReadTimeout, 2.34, "")
|
||||
assert.Equal(t, c.HTTPWriteTimeout, 1.23, "")
|
||||
assert.Equal(t, c.KeyFile, "/tmp/file.key", "")
|
||||
assert.Equal(t, c.BindAddr, "127.0.0.1:4003", "")
|
||||
assert.Equal(t, c.Peers, []string{"coreos.com:4001", "coreos.com:4002"}, "")
|
||||
@ -553,19 +561,12 @@ func TestConfigClusterRemoveDelayFlag(t *testing.T) {
|
||||
assert.Equal(t, c.Cluster.RemoveDelay, 100.0, "")
|
||||
}
|
||||
|
||||
// Ensures that the cluster sync interval can be parsed from the environment.
|
||||
func TestConfigClusterSyncIntervalEnv(t *testing.T) {
|
||||
withEnv("ETCD_CLUSTER_SYNC_INTERVAL", "10", func(c *Config) {
|
||||
assert.Nil(t, c.LoadEnv(), "")
|
||||
assert.Equal(t, c.Cluster.SyncInterval, 10.0, "")
|
||||
})
|
||||
}
|
||||
|
||||
// Ensures that the cluster sync interval flag can be parsed.
|
||||
func TestConfigClusterSyncIntervalFlag(t *testing.T) {
|
||||
c := New()
|
||||
assert.Nil(t, c.LoadFlags([]string{"-cluster-sync-interval", "10"}), "")
|
||||
assert.Equal(t, c.Cluster.SyncInterval, 10.0, "")
|
||||
assert.Nil(t, c.LoadFlags([]string{"-http-read-timeout", "2.34"}), "")
|
||||
assert.Equal(t, c.HTTPReadTimeout, 2.34, "")
|
||||
assert.Nil(t, c.LoadFlags([]string{"-http-write-timeout", "1.23"}), "")
|
||||
assert.Equal(t, c.HTTPWriteTimeout, 1.23, "")
|
||||
}
|
||||
|
||||
// Ensures that a system config field is overridden by a custom config field.
|
||||
|
13
etcd/etcd.go
13
etcd/etcd.go
@ -260,10 +260,19 @@ func (e *Etcd) Run() {
|
||||
|
||||
log.Infof("etcd server [name %s, listen on %s, advertised url %s]", e.Server.Name, e.Config.BindAddr, e.Server.URL())
|
||||
listener := server.NewListener(e.Config.EtcdTLSInfo().Scheme(), e.Config.BindAddr, etcdTLSConfig)
|
||||
e.server = &http.Server{Handler: &ModeHandler{e, serverHTTPHandler, standbyServerHTTPHandler}}
|
||||
|
||||
e.server = &http.Server{Handler: &ModeHandler{e, serverHTTPHandler, standbyServerHTTPHandler},
|
||||
ReadTimeout: time.Duration(e.Config.HTTPReadTimeout) * time.Second,
|
||||
WriteTimeout: time.Duration(e.Config.HTTPWriteTimeout) * time.Second,
|
||||
}
|
||||
|
||||
log.Infof("peer server [name %s, listen on %s, advertised url %s]", e.PeerServer.Config.Name, e.Config.Peer.BindAddr, e.PeerServer.Config.URL)
|
||||
peerListener := server.NewListener(e.Config.PeerTLSInfo().Scheme(), e.Config.Peer.BindAddr, peerTLSConfig)
|
||||
e.peerServer = &http.Server{Handler: &ModeHandler{e, peerServerHTTPHandler, http.NotFoundHandler()}}
|
||||
|
||||
e.peerServer = &http.Server{Handler: &ModeHandler{e, peerServerHTTPHandler, http.NotFoundHandler()},
|
||||
ReadTimeout: time.Duration(server.DefaultReadTimeout) * time.Second,
|
||||
WriteTimeout: time.Duration(server.DefaultWriteTimeout) * time.Second,
|
||||
}
|
||||
|
||||
wg := sync.WaitGroup{}
|
||||
wg.Add(2)
|
||||
|
@ -54,6 +54,7 @@ type CORSHandler struct {
|
||||
func (h *CORSHandler) addHeader(w http.ResponseWriter, origin string) {
|
||||
w.Header().Add("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE")
|
||||
w.Header().Add("Access-Control-Allow-Origin", origin)
|
||||
w.Header().Add("Access-Control-Allow-Headers", "accept, content-type")
|
||||
}
|
||||
|
||||
// ServeHTTP adds the correct CORS headers based on the origin and returns immediately
|
||||
|
@ -46,6 +46,7 @@ func (c *JoinCommand) NodeName() string {
|
||||
// applyJoin attempts to join a machine to the cluster.
|
||||
func applyJoin(c *JoinCommand, context raft.Context) (uint64, error) {
|
||||
ps, _ := context.Server().Context().(*PeerServer)
|
||||
ps.raftServer.FlushCommitIndex()
|
||||
commitIndex := context.CommitIndex()
|
||||
|
||||
// Make sure we're not getting a cached value from the registry.
|
||||
|
@ -3,10 +3,16 @@ package server
|
||||
import (
|
||||
"crypto/tls"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"github.com/coreos/etcd/log"
|
||||
)
|
||||
|
||||
const (
|
||||
DefaultReadTimeout = float64((5 * time.Minute) / time.Second)
|
||||
DefaultWriteTimeout = float64((5 * time.Minute) / time.Second)
|
||||
)
|
||||
|
||||
// TLSServerConfig generates tls configuration based on TLSInfo
|
||||
// If any error happens, this function will call log.Fatal
|
||||
func TLSServerConfig(info *TLSInfo) *tls.Config {
|
||||
|
@ -772,9 +772,9 @@ func (s *PeerServer) startRoutine(f func()) {
|
||||
func (s *PeerServer) monitorSnapshot() {
|
||||
for {
|
||||
timer := time.NewTimer(s.snapConf.checkingInterval)
|
||||
defer timer.Stop()
|
||||
select {
|
||||
case <-s.closeChan:
|
||||
timer.Stop()
|
||||
return
|
||||
case <-timer.C:
|
||||
}
|
||||
@ -807,6 +807,8 @@ func (s *PeerServer) monitorSync() {
|
||||
// monitorTimeoutThreshold groups timeout threshold events together and prints
|
||||
// them as a single log line.
|
||||
func (s *PeerServer) monitorTimeoutThreshold() {
|
||||
ticker := time.NewTicker(ThresholdMonitorTimeout)
|
||||
defer ticker.Stop()
|
||||
for {
|
||||
select {
|
||||
case <-s.closeChan:
|
||||
@ -815,12 +817,10 @@ func (s *PeerServer) monitorTimeoutThreshold() {
|
||||
log.Infof("%s: warning: heartbeat near election timeout: %v", s.Config.Name, value)
|
||||
}
|
||||
|
||||
timer := time.NewTimer(ThresholdMonitorTimeout)
|
||||
defer timer.Stop()
|
||||
select {
|
||||
case <-s.closeChan:
|
||||
return
|
||||
case <-timer.C:
|
||||
case <-ticker.C:
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -828,13 +828,13 @@ func (s *PeerServer) monitorTimeoutThreshold() {
|
||||
// monitorActiveSize has the leader periodically check the status of cluster
|
||||
// nodes and swaps them out for standbys as needed.
|
||||
func (s *PeerServer) monitorActiveSize() {
|
||||
ticker := time.NewTicker(ActiveMonitorTimeout)
|
||||
defer ticker.Stop()
|
||||
for {
|
||||
timer := time.NewTimer(ActiveMonitorTimeout)
|
||||
defer timer.Stop()
|
||||
select {
|
||||
case <-s.closeChan:
|
||||
return
|
||||
case <-timer.C:
|
||||
case <-ticker.C:
|
||||
}
|
||||
|
||||
// Ignore while this peer is not a leader.
|
||||
@ -864,13 +864,13 @@ func (s *PeerServer) monitorActiveSize() {
|
||||
|
||||
// monitorPeerActivity has the leader periodically for dead nodes and demotes them.
|
||||
func (s *PeerServer) monitorPeerActivity() {
|
||||
ticker := time.NewTicker(PeerActivityMonitorTimeout)
|
||||
defer ticker.Stop()
|
||||
for {
|
||||
timer := time.NewTimer(PeerActivityMonitorTimeout)
|
||||
defer timer.Stop()
|
||||
select {
|
||||
case <-s.closeChan:
|
||||
return
|
||||
case <-timer.C:
|
||||
case <-ticker.C:
|
||||
}
|
||||
|
||||
// Ignore while this peer is not a leader.
|
||||
|
@ -1,3 +1,3 @@
|
||||
package server
|
||||
|
||||
const ReleaseVersion = "0.4.4"
|
||||
const ReleaseVersion = "0.4.9"
|
||||
|
@ -3,8 +3,10 @@ package server
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/http/pprof"
|
||||
"path"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@ -135,6 +137,7 @@ func (s *Server) installV2(r *mux.Router) {
|
||||
s.handleFunc(r2, "/v2/stats/leader", s.GetLeaderStatsHandler).Methods("GET", "HEAD")
|
||||
s.handleFunc(r2, "/v2/stats/store", s.GetStoreStatsHandler).Methods("GET", "HEAD")
|
||||
s.handleFunc(r2, "/v2/speedTest", s.SpeedTestHandler).Methods("GET", "HEAD")
|
||||
s.handleFunc(r2, "/v2/migration/snapshot", s.SnapshotHandler).Methods("GET")
|
||||
}
|
||||
|
||||
func (s *Server) installMod(r *mux.Router) {
|
||||
@ -311,6 +314,7 @@ func (s *Server) GetPeersHandler(w http.ResponseWriter, req *http.Request) error
|
||||
|
||||
// Retrieves stats on the Raft server.
|
||||
func (s *Server) GetStatsHandler(w http.ResponseWriter, req *http.Request) error {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.Write(s.peerServer.Stats())
|
||||
return nil
|
||||
}
|
||||
@ -318,21 +322,19 @@ func (s *Server) GetStatsHandler(w http.ResponseWriter, req *http.Request) error
|
||||
// Retrieves stats on the leader.
|
||||
func (s *Server) GetLeaderStatsHandler(w http.ResponseWriter, req *http.Request) error {
|
||||
if s.peerServer.RaftServer().State() == raft.Leader {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.Write(s.peerServer.PeerStats())
|
||||
return nil
|
||||
}
|
||||
|
||||
leader := s.peerServer.RaftServer().Leader()
|
||||
if leader == "" {
|
||||
return etcdErr.NewError(300, "", s.Store().Index())
|
||||
}
|
||||
hostname, _ := s.registry.ClientURL(leader)
|
||||
uhttp.Redirect(hostname, w, req)
|
||||
w.WriteHeader(http.StatusForbidden)
|
||||
w.Write([]byte("not current leader"))
|
||||
return nil
|
||||
}
|
||||
|
||||
// Retrieves stats on the leader.
|
||||
func (s *Server) GetStoreStatsHandler(w http.ResponseWriter, req *http.Request) error {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.Write(s.store.JsonStats())
|
||||
return nil
|
||||
}
|
||||
@ -360,6 +362,41 @@ func (s *Server) SpeedTestHandler(w http.ResponseWriter, req *http.Request) erro
|
||||
return nil
|
||||
}
|
||||
|
||||
// SnapshotHandler forces etcd store to do a snapshot. If the disk parameter is set, the snapshot
|
||||
// will be written to disk at data-dir/index-migrate.snap. Or the snapshot will be returned as
|
||||
// http body.
|
||||
func (s *Server) SnapshotHandler(w http.ResponseWriter, req *http.Request) error {
|
||||
data, err := s.Store().Save()
|
||||
if err != nil {
|
||||
http.Error(w, "failed to create snapshot: "+err.Error(), http.StatusInternalServerError)
|
||||
log.Warn("Failed to create snapshot:" + err.Error())
|
||||
return nil
|
||||
}
|
||||
|
||||
disk := req.FormValue("disk")
|
||||
if disk == "true" {
|
||||
name := fmt.Sprintf("%d-migrate.snap", s.peerServer.RaftServer().CommitIndex())
|
||||
err = ioutil.WriteFile(path.Join(s.peerServer.RaftServer().Path(), name), data, 0600)
|
||||
if err != nil {
|
||||
http.Error(w, "failed to save snapshot: "+err.Error(), http.StatusInternalServerError)
|
||||
log.Warn("server: failed to save snapshot: " + err.Error())
|
||||
return nil
|
||||
}
|
||||
log.Infof("server: saved snapshot file %s successfully", name)
|
||||
return nil
|
||||
}
|
||||
if disk != "" && disk != "false" {
|
||||
http.Error(w, "invalid parameter: disk="+disk, http.StatusBadRequest)
|
||||
return nil
|
||||
}
|
||||
|
||||
_, err = w.Write(data)
|
||||
if err != nil {
|
||||
log.Warnf("server: failed to write snapshot to %s: %v", req.RemoteAddr, err.Error())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Retrieves metrics from bucket
|
||||
func (s *Server) GetMetricsHandler(w http.ResponseWriter, req *http.Request) error {
|
||||
(*s.metrics).Dump(w)
|
||||
|
@ -30,6 +30,7 @@ type StandbyServerConfig struct {
|
||||
}
|
||||
|
||||
type standbyInfo struct {
|
||||
// stay running in standby mode
|
||||
Running bool
|
||||
Cluster []*machineMessage
|
||||
SyncInterval float64
|
||||
@ -78,12 +79,16 @@ func (s *StandbyServer) Start() {
|
||||
s.removeNotify = make(chan bool)
|
||||
s.closeChan = make(chan bool)
|
||||
|
||||
s.Running = true
|
||||
if err := s.saveInfo(); err != nil {
|
||||
log.Warnf("error saving cluster info for standby")
|
||||
}
|
||||
|
||||
s.routineGroup.Add(1)
|
||||
go func() {
|
||||
defer s.routineGroup.Done()
|
||||
s.monitorCluster()
|
||||
}()
|
||||
s.Running = true
|
||||
}
|
||||
|
||||
// Stop stops the server gracefully.
|
||||
@ -97,11 +102,6 @@ func (s *StandbyServer) Stop() {
|
||||
|
||||
close(s.closeChan)
|
||||
s.routineGroup.Wait()
|
||||
|
||||
if err := s.saveInfo(); err != nil {
|
||||
log.Warnf("error saving cluster info for standby")
|
||||
}
|
||||
s.Running = false
|
||||
}
|
||||
|
||||
// RemoveNotify notifies the server is removed from standby mode and ready
|
||||
@ -178,13 +178,13 @@ func (s *StandbyServer) redirectRequests(w http.ResponseWriter, r *http.Request)
|
||||
// monitorCluster assumes that the machine has tried to join the cluster and
|
||||
// failed, so it waits for the interval at the beginning.
|
||||
func (s *StandbyServer) monitorCluster() {
|
||||
ticker := time.NewTicker(time.Duration(int64(s.SyncInterval * float64(time.Second))))
|
||||
defer ticker.Stop()
|
||||
for {
|
||||
timer := time.NewTimer(time.Duration(int64(s.SyncInterval * float64(time.Second))))
|
||||
defer timer.Stop()
|
||||
select {
|
||||
case <-s.closeChan:
|
||||
return
|
||||
case <-timer.C:
|
||||
case <-ticker.C:
|
||||
}
|
||||
|
||||
if err := s.syncCluster(nil); err != nil {
|
||||
@ -204,6 +204,10 @@ func (s *StandbyServer) monitorCluster() {
|
||||
}
|
||||
|
||||
log.Infof("join through leader %v", leader.PeerURL)
|
||||
s.Running = false
|
||||
if err := s.saveInfo(); err != nil {
|
||||
log.Warnf("error saving cluster info for standby")
|
||||
}
|
||||
go func() {
|
||||
s.Stop()
|
||||
close(s.removeNotify)
|
||||
|
@ -53,7 +53,6 @@ Other Options:
|
||||
-max-result-buffer Max size of the result buffer.
|
||||
-max-retry-attempts Number of times a node will try to join a cluster.
|
||||
-retry-interval Seconds to wait between cluster join retry attempts.
|
||||
-max-cluster-size Maximum number of nodes in the cluster.
|
||||
-snapshot=false Disable log snapshots
|
||||
-snapshot-count Number of transactions before issuing a snapshot.
|
||||
-cluster-active-size Number of active nodes in the cluster.
|
||||
|
@ -17,6 +17,14 @@ func GetHandler(w http.ResponseWriter, req *http.Request, s Server) error {
|
||||
vars := mux.Vars(req)
|
||||
key := "/" + vars["key"]
|
||||
|
||||
recursive := (req.FormValue("recursive") == "true")
|
||||
sort := (req.FormValue("sorted") == "true")
|
||||
|
||||
if req.FormValue("quorum") == "true" {
|
||||
c := s.Store().CommandFactory().CreateGetCommand(key, recursive, sort)
|
||||
return s.Dispatch(c, w, req)
|
||||
}
|
||||
|
||||
// Help client to redirect the request to the current leader
|
||||
if req.FormValue("consistent") == "true" && s.State() != raft.Leader {
|
||||
leader := s.Leader()
|
||||
@ -35,8 +43,6 @@ func GetHandler(w http.ResponseWriter, req *http.Request, s Server) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
recursive := (req.FormValue("recursive") == "true")
|
||||
sort := (req.FormValue("sorted") == "true")
|
||||
waitIndex := req.FormValue("waitIndex")
|
||||
stream := (req.FormValue("stream") == "true")
|
||||
|
||||
@ -68,6 +74,7 @@ func handleWatch(key string, recursive, stream bool, waitIndex string, w http.Re
|
||||
closeChan := cn.CloseNotify()
|
||||
|
||||
writeHeaders(w, s)
|
||||
w.(http.Flusher).Flush()
|
||||
|
||||
if stream {
|
||||
// watcher hub will not help to remove stream watcher
|
||||
|
@ -24,6 +24,7 @@ type CommandFactory interface {
|
||||
prevIndex uint64, expireTime time.Time) raft.Command
|
||||
CreateCompareAndDeleteCommand(key string, prevValue string, prevIndex uint64) raft.Command
|
||||
CreateSyncCommand(now time.Time) raft.Command
|
||||
CreateGetCommand(key string, recursive, sorted bool) raft.Command
|
||||
}
|
||||
|
||||
// RegisterCommandFactory adds a command factory to the global registry.
|
||||
|
@ -89,3 +89,11 @@ func (f *CommandFactory) CreateSyncCommand(now time.Time) raft.Command {
|
||||
Time: time.Now(),
|
||||
}
|
||||
}
|
||||
|
||||
func (f *CommandFactory) CreateGetCommand(key string, recursive, sorted bool) raft.Command {
|
||||
return &GetCommand{
|
||||
Key: key,
|
||||
Recursive: recursive,
|
||||
Sorted: sorted,
|
||||
}
|
||||
}
|
||||
|
35
store/v2/get_command.go
Normal file
35
store/v2/get_command.go
Normal file
@ -0,0 +1,35 @@
|
||||
package v2
|
||||
|
||||
import (
|
||||
"github.com/coreos/etcd/log"
|
||||
"github.com/coreos/etcd/store"
|
||||
"github.com/coreos/etcd/third_party/github.com/goraft/raft"
|
||||
)
|
||||
|
||||
func init() {
|
||||
raft.RegisterCommand(&GetCommand{})
|
||||
}
|
||||
|
||||
// The GetCommand gets a key from the Store.
|
||||
type GetCommand struct {
|
||||
Key string `json:"key"`
|
||||
Recursive bool `json:"recursive"`
|
||||
Sorted bool `json:sorted`
|
||||
}
|
||||
|
||||
// The name of the get command in the log
|
||||
func (c *GetCommand) CommandName() string {
|
||||
return "etcd:get"
|
||||
}
|
||||
|
||||
// Get the key
|
||||
func (c *GetCommand) Apply(context raft.Context) (interface{}, error) {
|
||||
s, _ := context.Server().StateMachine().(store.Store)
|
||||
e, err := s.Get(c.Key, c.Recursive, c.Sorted)
|
||||
|
||||
if err != nil {
|
||||
log.Debug(err)
|
||||
return nil, err
|
||||
}
|
||||
return e, nil
|
||||
}
|
Reference in New Issue
Block a user