Compare commits
93 Commits
Author | SHA1 | Date | |
---|---|---|---|
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 | |||
4fb6087f4a | |||
5524131a9e | |||
3efb4d837b | |||
20147c5357 | |||
973bde9a07 | |||
494d2c67aa | |||
fb32a999a6 | |||
d2f5934aa1 | |||
4f3fb5a702 | |||
9f5ec7732e | |||
774cf34827 | |||
92df44276d | |||
eb00f200d3 | |||
38d16775ab | |||
690fd12b07 | |||
b31483b2be | |||
e9a21dda4b | |||
2134036942 | |||
6bd2ee4c49 | |||
fcd429467e | |||
e5e759b962 | |||
d8a08f53e3 | |||
3e95bf0fa3 | |||
0d2512cb99 | |||
a29f6fb799 | |||
f4f429d4e3 | |||
fc2afe1ed2 | |||
24a442383b | |||
f387bf8464 | |||
83b06c0715 | |||
75dc10c39d | |||
66acf8a4e9 | |||
1359d29fa4 | |||
dc1f4adcd0 | |||
e01a1f70c3 | |||
2e4ea503b0 | |||
c7aef5fdf2 | |||
c4605160c5 | |||
054de85da2 |
18
CHANGELOG
18
CHANGELOG
@ -1,3 +1,21 @@
|
|||||||
|
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)
|
||||||
|
* Various documentation improvements (#858, #851, #847)
|
||||||
|
|
||||||
v0.4.3
|
v0.4.3
|
||||||
* Avoid panic() on truncated or unexpected log data (#834, #833)
|
* Avoid panic() on truncated or unexpected log data (#834, #833)
|
||||||
* Fix missing stats field (#807)
|
* Fix missing stats field (#807)
|
||||||
|
@ -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.
|
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
|
this uses tmux to setup a test cluster that you can easily kill and
|
||||||
start for debugging.
|
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>
|
<BLANK LINE>
|
||||||
<body>
|
<why this change was made>
|
||||||
<BLANK LINE>
|
<BLANK LINE>
|
||||||
<footer>
|
<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
|
The first line is the subject and should be no longer than 70 characters, the
|
||||||
in various git tools.
|
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
|
||||||
### Subject line
|
git tools.
|
||||||
|
|
||||||
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#).
|
|
||||||
|
@ -2,7 +2,7 @@ FROM ubuntu:12.04
|
|||||||
# Let's install go just like Docker (from source).
|
# Let's install go just like Docker (from source).
|
||||||
RUN apt-get update -q
|
RUN apt-get update -q
|
||||||
RUN DEBIAN_FRONTEND=noninteractive apt-get install -qy build-essential curl git
|
RUN DEBIAN_FRONTEND=noninteractive apt-get install -qy build-essential curl git
|
||||||
RUN curl -s https://go.googlecode.com/files/go1.2.1.src.tar.gz | tar -v -C /usr/local -xz
|
RUN curl -s https://storage.googleapis.com/golang/go1.3.src.tar.gz | tar -v -C /usr/local -xz
|
||||||
RUN cd /usr/local/go/src && ./make.bash --no-clean 2>&1
|
RUN cd /usr/local/go/src && ./make.bash --no-clean 2>&1
|
||||||
ENV PATH /usr/local/go/bin:$PATH
|
ENV PATH /usr/local/go/bin:$PATH
|
||||||
ADD . /opt/etcd
|
ADD . /opt/etcd
|
||||||
|
@ -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 `-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.
|
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
|
## Key Space Operations
|
||||||
|
|
||||||
The primary API of etcd is a hierarchical key space.
|
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 Consistency
|
||||||
|
|
||||||
|
#### Read from the Master
|
||||||
|
|
||||||
Followers in a cluster can be behind the leader in their copy of the keyspace.
|
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.
|
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.
|
By using the `consistent=true` flag in your GET requests, etcd will make sure you are talking to the current master.
|
||||||
@ -843,13 +853,26 @@ 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.
|
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.
|
Since F2 missed the most recent write, a client reading from F2 will have an out-of-date version of the keyspace.
|
||||||
|
|
||||||
## Lock Module (*Deprecated*)
|
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.
|
The lock module is used to serialize access to resources used by clients.
|
||||||
Multiple clients can attempt to acquire a lock but only one can have it at a time.
|
Multiple clients can attempt to acquire a lock but only one can have it at a time.
|
||||||
Once the lock is released, the next client waiting for the lock will receive it.
|
Once the lock is released, the next client waiting for the lock will receive it.
|
||||||
|
|
||||||
**Warning:** This module is deprecated at v0.4. See [Modules][modules] for more details.
|
**Warning:** This module is deprecated and removed at v0.4. See [Modules][modules] for more details.
|
||||||
|
|
||||||
|
|
||||||
### Acquiring a Lock
|
### Acquiring a Lock
|
||||||
|
@ -29,16 +29,16 @@ The v2 API has a lot of features, we will categorize them in a few categories:
|
|||||||
|
|
||||||
### Supported features matrix
|
### Supported features matrix
|
||||||
|
|
||||||
| Client| [go-etcd](https://github.com/coreos/go-etcd) | [jetcd](https://github.com/diwakergupta/jetcd) | [python-etcd](https://github.com/jplana/python-etcd) | [python-etcd-client](https://github.com/dsoprea/PythonEtcdClient) | [node-etcd](https://github.com/stianeikeland/node-etcd) | [nodejs-etcd](https://github.com/lavagetto/nodejs-etcd) | [etcd-ruby](https://github.com/ranjib/etcd-ruby) | [etcd-api](https://github.com/jdarcy/etcd-api) | [cetcd](https://github.com/dwwoelfel/cetcd) | [clj-etcd](https://github.com/rthomas/clj-etcd) | [etcetera](https://github.com/drusellers/etcetera)| [Etcd.jl](https://github.com/forio/Etcd.jl) |
|
| Client| [go-etcd](https://github.com/coreos/go-etcd) | [jetcd](https://github.com/diwakergupta/jetcd) | [python-etcd](https://github.com/jplana/python-etcd) | [python-etcd-client](https://github.com/dsoprea/PythonEtcdClient) | [node-etcd](https://github.com/stianeikeland/node-etcd) | [nodejs-etcd](https://github.com/lavagetto/nodejs-etcd) | [etcd-ruby](https://github.com/ranjib/etcd-ruby) | [etcd-api](https://github.com/jdarcy/etcd-api) | [cetcd](https://github.com/dwwoelfel/cetcd) | [clj-etcd](https://github.com/rthomas/clj-etcd) | [etcetera](https://github.com/drusellers/etcetera)| [Etcd.jl](https://github.com/forio/Etcd.jl) | [p5-etcd](https://metacpan.org/release/Etcd)
|
||||||
| --- | :---: | :---: | :---: | :---: | :---: | :---: | :---: | :---: | :---: | :---: | :---: | :---: |
|
| --- | :---: | :---: | :---: | :---: | :---: | :---: | :---: | :---: | :---: | :---: | :---: | :---: | :---: |
|
||||||
| **HTTPS Auth** | Y | Y | Y | Y | Y | Y | - | - | - | - | - | - |
|
| **HTTPS Auth** | Y | Y | Y | Y | Y | Y | - | - | - | - | - | - | - |
|
||||||
| **Reconnect** | Y | - | Y | Y | - | - | - | Y | - | - | - | - |
|
| **Reconnect** | Y | - | Y | Y | - | - | - | Y | - | - | - | - | - |
|
||||||
| **Mod/Lock** | - | - | Y | Y | - | - | - | - | - | - | - | Y |
|
| **Mod/Lock** | - | - | Y | Y | - | - | - | - | - | - | - | Y | - |
|
||||||
| **Mod/Leader** | - | - | - | Y | - | - | - | - | - | - | - | Y |
|
| **Mod/Leader** | - | - | - | Y | - | - | - | - | - | - | - | Y | - |
|
||||||
| **GET Features** | F | B | F | F | F | F | F | B | F | G | F | F |
|
| **GET Features** | F | B | F | F | F | F | F | B | F | G | F | F | F |
|
||||||
| **PUT Features** | F | B | F | F | F | F | F | G | F | G | F | F |
|
| **PUT Features** | F | B | F | F | F | F | F | G | F | G | F | F | F |
|
||||||
| **POST Features** | F | - | F | F | - | F | F | - | - | - | F | F |
|
| **POST Features** | F | - | F | F | - | F | F | - | - | - | F | F | F |
|
||||||
| **DEL Features** | F | B | F | F | F | F | F | B | G | B | F | F |
|
| **DEL Features** | F | B | F | F | F | F | F | B | G | B | F | F | F |
|
||||||
|
|
||||||
**Legend**
|
**Legend**
|
||||||
|
|
||||||
|
@ -8,6 +8,7 @@ For more information on how etcd can locate the cluster, see the [finding the cl
|
|||||||
|
|
||||||
Please note - at least 3 nodes are required for [cluster availability][optimal-cluster-size].
|
Please note - at least 3 nodes are required for [cluster availability][optimal-cluster-size].
|
||||||
|
|
||||||
|
[cluster-finding]: https://github.com/coreos/etcd/blob/master/Documentation/design/cluster-finding.md
|
||||||
[optimal-cluster-size]: https://github.com/coreos/etcd/blob/master/Documentation/optimal-cluster-size.md
|
[optimal-cluster-size]: https://github.com/coreos/etcd/blob/master/Documentation/optimal-cluster-size.md
|
||||||
|
|
||||||
## Using discovery.etcd.io
|
## Using discovery.etcd.io
|
||||||
@ -27,8 +28,8 @@ Here's a full example:
|
|||||||
```
|
```
|
||||||
TOKEN=$(curl https://discovery.etcd.io/new)
|
TOKEN=$(curl https://discovery.etcd.io/new)
|
||||||
./etcd -name instance1 -peer-addr 10.1.2.3:7001 -addr 10.1.2.3:4001 -discovery $TOKEN
|
./etcd -name instance1 -peer-addr 10.1.2.3:7001 -addr 10.1.2.3:4001 -discovery $TOKEN
|
||||||
./etcd -name instance2 -peer-addr 10.1.2.4:7002 -addr 10.1.2.4:4002 -discovery $TOKEN
|
./etcd -name instance2 -peer-addr 10.1.2.4:7001 -addr 10.1.2.4:4001 -discovery $TOKEN
|
||||||
./etcd -name instance3 -peer-addr 10.1.2.5:7002 -addr 10.1.2.5:4002 -discovery $TOKEN
|
./etcd -name instance3 -peer-addr 10.1.2.5:7001 -addr 10.1.2.5:4001 -discovery $TOKEN
|
||||||
```
|
```
|
||||||
|
|
||||||
## Running Your Own Discovery Endpoint
|
## Running Your Own Discovery Endpoint
|
||||||
@ -38,8 +39,8 @@ The discovery API communicates with a separate etcd cluster to store and retriev
|
|||||||
```
|
```
|
||||||
TOKEN="testcluster"
|
TOKEN="testcluster"
|
||||||
./etcd -name instance1 -peer-addr 10.1.2.3:7001 -addr 10.1.2.3:4001 -discovery http://10.10.10.10:4001/v2/keys/$TOKEN
|
./etcd -name instance1 -peer-addr 10.1.2.3:7001 -addr 10.1.2.3:4001 -discovery http://10.10.10.10:4001/v2/keys/$TOKEN
|
||||||
./etcd -name instance2 -peer-addr 10.1.2.4:7002 -addr 10.1.2.4:4002 -discovery http://10.10.10.10:4001/v2/keys/$TOKEN
|
./etcd -name instance2 -peer-addr 10.1.2.4:7001 -addr 10.1.2.4:4001 -discovery http://10.10.10.10:4001/v2/keys/$TOKEN
|
||||||
./etcd -name instance3 -peer-addr 10.1.2.5:7002 -addr 10.1.2.5:4002 -discovery http://10.10.10.10:4001/v2/keys/$TOKEN
|
./etcd -name instance3 -peer-addr 10.1.2.5:7001 -addr 10.1.2.5:4001 -discovery http://10.10.10.10:4001/v2/keys/$TOKEN
|
||||||
```
|
```
|
||||||
|
|
||||||
If you're interested in how to discovery API works behind the scenes, read about the [Discovery Protocol](https://github.com/coreos/etcd/blob/master/Documentation/discovery-protocol.md).
|
If you're interested in how to discovery API works behind the scenes, read about the [Discovery Protocol](https://github.com/coreos/etcd/blob/master/Documentation/discovery-protocol.md).
|
||||||
@ -52,8 +53,6 @@ The Discovery API submits the `-peer-addr` of each etcd instance to the configur
|
|||||||
|
|
||||||
The discovery API will automatically clean up the address of a stale peer that is no longer part of the cluster. The TTL for this process is a week, which should be long enough to handle any extremely long outage you may encounter. There is no harm in having stale peers in the list until they are cleaned up, since an etcd instance only needs to connect to one valid peer in the cluster to join.
|
The discovery API will automatically clean up the address of a stale peer that is no longer part of the cluster. The TTL for this process is a week, which should be long enough to handle any extremely long outage you may encounter. There is no harm in having stale peers in the list until they are cleaned up, since an etcd instance only needs to connect to one valid peer in the cluster to join.
|
||||||
|
|
||||||
[discovery-design]: https://github.com/coreos/etcd/blob/master/Documentation/design/cluster-finding.md
|
|
||||||
|
|
||||||
## Lifetime of a Discovery URL
|
## Lifetime of a Discovery URL
|
||||||
|
|
||||||
A discovery URL identifies a single etcd cluster. Do not re-use discovery URLs for new clusters.
|
A discovery URL identifies a single etcd cluster. Do not re-use discovery URLs for new clusters.
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
# Etcd Configuration
|
# Etcd Configuration
|
||||||
|
|
||||||
Configuration options can be set in three places:
|
## Node Configuration
|
||||||
|
|
||||||
|
Individual node configuration options can be set in three places:
|
||||||
|
|
||||||
1. Command line flags
|
1. Command line flags
|
||||||
2. Environment variables
|
2. Environment variables
|
||||||
@ -10,17 +12,29 @@ Options set on the command line take precedence over all other sources.
|
|||||||
Options set in environment variables take precedence over options set in
|
Options set in environment variables take precedence over options set in
|
||||||
configuration files.
|
configuration files.
|
||||||
|
|
||||||
|
## Cluster Configuration
|
||||||
|
|
||||||
|
Cluster-wide settings are configured via the `/config` admin endpoint and additionally in the configuration file. Values contained in the configuration file will seed the cluster setting with the provided value. After the cluster is running, only the admin endpoint is used.
|
||||||
|
|
||||||
|
The full documentation is contained in the [API docs](https://github.com/coreos/etcd/blob/master/Documentation/api.md#cluster-config).
|
||||||
|
|
||||||
|
* `activeSize` - the maximum number of peers that can participate in the consensus protocol. Other peers will join as standbys.
|
||||||
|
* `removeDelay` - the minimum time in seconds that a machine has been observed to be unresponsive before it is removed from the cluster.
|
||||||
|
* `syncInterval` - the amount of time in seconds between cluster sync when it runs in standby mode.
|
||||||
|
|
||||||
## Command Line Flags
|
## Command Line Flags
|
||||||
|
|
||||||
### Required
|
### Required
|
||||||
|
|
||||||
* `-name` - The node name. Defaults to the hostname.
|
* `-name` - The node name. Defaults to a UUID.
|
||||||
|
|
||||||
### Optional
|
### Optional
|
||||||
|
|
||||||
* `-addr` - The advertised public hostname:port for client communication. Defaults to `127.0.0.1:4001`.
|
* `-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"`).
|
* `-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` - 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.
|
* `-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.
|
* `-ca-file` - The path of the client CAFile. Enables client cert authentication when present.
|
||||||
@ -30,17 +44,20 @@ configuration files.
|
|||||||
* `-cors` - A comma separated white list of origins for cross-origin resource sharing.
|
* `-cors` - A comma separated white list of origins for cross-origin resource sharing.
|
||||||
* `-cpuprofile` - The path to a file to output CPU profile data. Enables CPU profiling when present.
|
* `-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.
|
* `-data-dir` - The directory to store log and snapshot. Defaults to the current working directory.
|
||||||
|
* `-internal-binary-dir` - The path to the etcd internal binary directory. Defaults to `/usr/libexec/etcd/internal_versions/`.
|
||||||
* `-max-result-buffer` - The max size of result buffer. Defaults to `1024`.
|
* `-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`.
|
* `-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-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-ca-file` - The path of the CAFile. Enables client/peer cert authentication when present.
|
||||||
* `-peer-cert-file` - The cert file of the server.
|
* `-peer-cert-file` - The cert file of the server.
|
||||||
* `-peer-key-file` - The key file of the server.
|
* `-peer-key-file` - The key file of the server.
|
||||||
* `-peer-election-timeout` - The number of milliseconds to wait before the leader is declared unhealthy.
|
* `-peer-election-timeout` - The number of milliseconds to wait before the leader is declared unhealthy.
|
||||||
* `-peer-heartbeat-interval` - The number of milliseconds in between heartbeat requests
|
* `-peer-heartbeat-interval` - The number of milliseconds in between heartbeat requests
|
||||||
* `-snapshot=false` - Disable log snapshots. Defaults to `true`.
|
* `-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 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`.
|
* `-v` - Enable verbose logging. Defaults to `false`.
|
||||||
* `-vv` - Enable very verbose logging. Defaults to `false`.
|
* `-vv` - Enable very verbose logging. Defaults to `false`.
|
||||||
* `-version` - Print the version and exit.
|
* `-version` - Print the version and exit.
|
||||||
@ -59,6 +76,8 @@ cors = []
|
|||||||
cpu_profile_file = ""
|
cpu_profile_file = ""
|
||||||
data_dir = "."
|
data_dir = "."
|
||||||
discovery = "http://etcd.local:4001/v2/keys/_etcd/registry/examplecluster"
|
discovery = "http://etcd.local:4001/v2/keys/_etcd/registry/examplecluster"
|
||||||
|
http_read_timeout = 10
|
||||||
|
http_write_timeout = 10
|
||||||
key_file = ""
|
key_file = ""
|
||||||
peers = []
|
peers = []
|
||||||
peers_file = ""
|
peers_file = ""
|
||||||
@ -76,6 +95,11 @@ bind_addr = "127.0.0.1:7001"
|
|||||||
ca_file = ""
|
ca_file = ""
|
||||||
cert_file = ""
|
cert_file = ""
|
||||||
key_file = ""
|
key_file = ""
|
||||||
|
|
||||||
|
[cluster]
|
||||||
|
active_size = 9
|
||||||
|
remove_delay = 1800.0
|
||||||
|
sync_interval = 5.0
|
||||||
```
|
```
|
||||||
|
|
||||||
## Environment Variables
|
## Environment Variables
|
||||||
@ -89,7 +113,10 @@ key_file = ""
|
|||||||
* `ETCD_CPU_PROFILE_FILE`
|
* `ETCD_CPU_PROFILE_FILE`
|
||||||
* `ETCD_DATA_DIR`
|
* `ETCD_DATA_DIR`
|
||||||
* `ETCD_DISCOVERY`
|
* `ETCD_DISCOVERY`
|
||||||
|
* `ETCD_CLUSTER_HTTP_READ_TIMEOUT`
|
||||||
|
* `ETCD_CLUSTER_HTTP_WRITE_TIMEOUT`
|
||||||
* `ETCD_KEY_FILE`
|
* `ETCD_KEY_FILE`
|
||||||
|
* `ETCD_INTERNAL_BINARY_DIR`
|
||||||
* `ETCD_PEERS`
|
* `ETCD_PEERS`
|
||||||
* `ETCD_PEERS_FILE`
|
* `ETCD_PEERS_FILE`
|
||||||
* `ETCD_MAX_CLUSTER_SIZE`
|
* `ETCD_MAX_CLUSTER_SIZE`
|
||||||
@ -105,3 +132,6 @@ key_file = ""
|
|||||||
* `ETCD_PEER_CERT_FILE`
|
* `ETCD_PEER_CERT_FILE`
|
||||||
* `ETCD_PEER_KEY_FILE`
|
* `ETCD_PEER_KEY_FILE`
|
||||||
* `ETCD_PEER_ELECTION_TIMEOUT`
|
* `ETCD_PEER_ELECTION_TIMEOUT`
|
||||||
|
* `ETCD_CLUSTER_ACTIVE_SIZE`
|
||||||
|
* `ETCD_CLUSTER_REMOVE_DELAY`
|
||||||
|
* `ETCD_CLUSTER_SYNC_INTERVAL`
|
||||||
|
@ -17,7 +17,6 @@
|
|||||||
|
|
||||||
**Python libraries**
|
**Python libraries**
|
||||||
|
|
||||||
- [transitorykris/etcd-py](https://github.com/transitorykris/etcd-py)
|
|
||||||
- [jplana/python-etcd](https://github.com/jplana/python-etcd) - Supports v2
|
- [jplana/python-etcd](https://github.com/jplana/python-etcd) - Supports v2
|
||||||
- [russellhaering/txetcd](https://github.com/russellhaering/txetcd) - a Twisted Python library
|
- [russellhaering/txetcd](https://github.com/russellhaering/txetcd) - a Twisted Python library
|
||||||
- [cholcombe973/autodock](https://github.com/cholcombe973/autodock) - A docker deployment automation tool
|
- [cholcombe973/autodock](https://github.com/cholcombe973/autodock) - A docker deployment automation tool
|
||||||
@ -90,3 +89,6 @@ A detailed recap of client functionalities can be found in the [clients compatib
|
|||||||
- [configdb](https://git.autistici.org/ai/configdb/tree/master) - A REST relational abstraction on top of arbitrary database backends, aimed at storing configs and inventories.
|
- [configdb](https://git.autistici.org/ai/configdb/tree/master) - A REST relational abstraction on top of arbitrary database backends, aimed at storing configs and inventories.
|
||||||
- [scrz](https://github.com/scrz/scrz) - Container manager, stores configuration in etcd.
|
- [scrz](https://github.com/scrz/scrz) - Container manager, stores configuration in etcd.
|
||||||
- [fleet](https://github.com/coreos/fleet) - Distributed init system
|
- [fleet](https://github.com/coreos/fleet) - Distributed init system
|
||||||
|
- [GoogleCloudPlatform/kubernetes](https://github.com/GoogleCloudPlatform/kubernetes) - Container cluster manager.
|
||||||
|
- [mailgun/vulcand](https://github.com/mailgun/vulcand) - HTTP proxy that uses etcd as a configuration backend.
|
||||||
|
- [duedil-ltd/discodns](https://github.com/duedil-ltd/discodns) - Simple DNS nameserver using etcd as a database for names and records.
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
## Modules
|
## Modules
|
||||||
|
|
||||||
etcd has a number of modules that are built on top of the core etcd API.
|
etcd has a number of modules that are built on top of the core etcd API.
|
||||||
These modules provide things like dashboards, locks and leader election.
|
These modules provide things like dashboards, locks and leader election (removed).
|
||||||
|
|
||||||
**Warning**: Modules are deprecated from v0.4 until we have a solid base we can apply them back onto.
|
**Warning**: Modules are deprecated from v0.4 until we have a solid base we can apply them back onto.
|
||||||
For now, we are choosing to focus on raft algorithm and core etcd to make sure that it works correctly and fast.
|
For now, we are choosing to focus on raft algorithm and core etcd to make sure that it works correctly and fast.
|
||||||
@ -81,7 +81,7 @@ curl -X DELETE http://127.0.0.1:4001/mod/v2/lock/customer1?value=bar
|
|||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
### Leader Election
|
### Leader Election (Deprecated and Removed in 0.4)
|
||||||
|
|
||||||
The Leader Election module wraps the Lock module to allow clients to come to consensus on a single value.
|
The Leader Election module wraps the Lock module to allow clients to come to consensus on a single value.
|
||||||
This is useful when you want one server to process at a time but allow other servers to fail over.
|
This is useful when you want one server to process at a time but allow other servers to fail over.
|
||||||
|
@ -28,11 +28,11 @@ The other important cluster optimization is to always have an odd active cluster
|
|||||||
|--------------|------------|-------------------|
|
|--------------|------------|-------------------|
|
||||||
| 1 peers | 1 peers | None |
|
| 1 peers | 1 peers | None |
|
||||||
| 3 peers | 2 peers | 1 peer |
|
| 3 peers | 2 peers | 1 peer |
|
||||||
| 4 peers | 3 peers | 2 peers |
|
| 4 peers | 3 peers | 1 peer |
|
||||||
| 5 peers | 3 peers | **3 peers** |
|
| 5 peers | 3 peers | **2 peers** |
|
||||||
| 6 peers | 4 peers | 2 peers |
|
| 6 peers | 4 peers | 2 peers |
|
||||||
| 7 peers | 4 peers | **3 peers** |
|
| 7 peers | 4 peers | **3 peers** |
|
||||||
| 8 peers | 5 peers | 3 peers |
|
| 8 peers | 5 peers | 3 peers |
|
||||||
| 9 peers | 5 peers | **4 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.
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
ectd is being used successfully by many companies in production. It is,
|
etcd is being used successfully by many companies in production. It is,
|
||||||
however, under active development and systems like etcd are difficult to get
|
however, under active development and systems like etcd are difficult to get
|
||||||
correct. If you are comfortable with bleeding-edge software please use etcd and
|
correct. If you are comfortable with bleeding-edge software please use etcd and
|
||||||
provide us with the feedback and testing young software needs.
|
provide us with the feedback and testing young software needs.
|
||||||
|
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
|
# etcd
|
||||||
|
|
||||||
README version 0.4.3
|
README version 0.4.8
|
||||||
|
|
||||||
A highly-available key value store for shared configuration and service discovery.
|
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:
|
etcd is inspired by [Apache ZooKeeper][zookeeper] and [doozer][doozer], with a focus on being:
|
||||||
@ -95,7 +95,7 @@ You have successfully started an etcd on a single machine and written a key to t
|
|||||||
## Contact
|
## Contact
|
||||||
|
|
||||||
- Mailing list: [etcd-dev](https://groups.google.com/forum/?hl=en#!forum/etcd-dev)
|
- Mailing list: [etcd-dev](https://groups.google.com/forum/?hl=en#!forum/etcd-dev)
|
||||||
- IRC: #[coreos](irc://irc.freenode.org:6667/#coreos) oon freenode.org
|
- IRC: #[coreos](irc://irc.freenode.org:6667/#coreos) on freenode.org
|
||||||
- Planning/Roadmap: [milestones](https://github.com/coreos/etcd/issues/milestones)
|
- Planning/Roadmap: [milestones](https://github.com/coreos/etcd/issues/milestones)
|
||||||
- Bugs: [issues](https://github.com/coreos/etcd/issues)
|
- Bugs: [issues](https://github.com/coreos/etcd/issues)
|
||||||
|
|
||||||
|
@ -24,6 +24,9 @@ import (
|
|||||||
// The default location for the etcd configuration file.
|
// The default location for the etcd configuration file.
|
||||||
const DefaultSystemConfigPath = "/etc/etcd/etcd.conf"
|
const DefaultSystemConfigPath = "/etc/etcd/etcd.conf"
|
||||||
|
|
||||||
|
// The default location of the etcd internal binary directory.
|
||||||
|
const DefaultInternalBinaryDir = "/usr/libexec/etcd/internal_versions/"
|
||||||
|
|
||||||
// A lookup of deprecated flags to their new flag name.
|
// A lookup of deprecated flags to their new flag name.
|
||||||
var newFlagNameLookup = map[string]string{
|
var newFlagNameLookup = map[string]string{
|
||||||
"C": "peers",
|
"C": "peers",
|
||||||
@ -36,7 +39,7 @@ var newFlagNameLookup = map[string]string{
|
|||||||
"d": "data-dir",
|
"d": "data-dir",
|
||||||
"m": "max-result-buffer",
|
"m": "max-result-buffer",
|
||||||
"r": "max-retry-attempts",
|
"r": "max-retry-attempts",
|
||||||
"maxsize": "max-cluster-size",
|
"maxsize": "cluster-active-size",
|
||||||
"clientCAFile": "ca-file",
|
"clientCAFile": "ca-file",
|
||||||
"clientCert": "cert-file",
|
"clientCert": "cert-file",
|
||||||
"clientKey": "key-file",
|
"clientKey": "key-file",
|
||||||
@ -45,36 +48,40 @@ var newFlagNameLookup = map[string]string{
|
|||||||
"serverKey": "peer-key-file",
|
"serverKey": "peer-key-file",
|
||||||
"snapshotCount": "snapshot-count",
|
"snapshotCount": "snapshot-count",
|
||||||
"peer-heartbeat-timeout": "peer-heartbeat-interval",
|
"peer-heartbeat-timeout": "peer-heartbeat-interval",
|
||||||
|
"max-cluster-size": "cluster-active-size",
|
||||||
}
|
}
|
||||||
|
|
||||||
// Config represents the server configuration.
|
// Config represents the server configuration.
|
||||||
type Config struct {
|
type Config struct {
|
||||||
SystemPath string
|
SystemPath string
|
||||||
|
|
||||||
Addr string `toml:"addr" env:"ETCD_ADDR"`
|
Addr string `toml:"addr" env:"ETCD_ADDR"`
|
||||||
BindAddr string `toml:"bind_addr" env:"ETCD_BIND_ADDR"`
|
BindAddr string `toml:"bind_addr" env:"ETCD_BIND_ADDR"`
|
||||||
CAFile string `toml:"ca_file" env:"ETCD_CA_FILE"`
|
CAFile string `toml:"ca_file" env:"ETCD_CA_FILE"`
|
||||||
CertFile string `toml:"cert_file" env:"ETCD_CERT_FILE"`
|
CertFile string `toml:"cert_file" env:"ETCD_CERT_FILE"`
|
||||||
CPUProfileFile string
|
CPUProfileFile string
|
||||||
CorsOrigins []string `toml:"cors" env:"ETCD_CORS"`
|
CorsOrigins []string `toml:"cors" env:"ETCD_CORS"`
|
||||||
DataDir string `toml:"data_dir" env:"ETCD_DATA_DIR"`
|
DataDir string `toml:"data_dir" env:"ETCD_DATA_DIR"`
|
||||||
Discovery string `toml:"discovery" env:"ETCD_DISCOVERY"`
|
Discovery string `toml:"discovery" env:"ETCD_DISCOVERY"`
|
||||||
Force bool
|
Force bool
|
||||||
KeyFile string `toml:"key_file" env:"ETCD_KEY_FILE"`
|
KeyFile string `toml:"key_file" env:"ETCD_KEY_FILE"`
|
||||||
Peers []string `toml:"peers" env:"ETCD_PEERS"`
|
HTTPReadTimeout float64 `toml:"http_read_timeout" env:"ETCD_HTTP_READ_TIMEOUT"`
|
||||||
PeersFile string `toml:"peers_file" env:"ETCD_PEERS_FILE"`
|
HTTPWriteTimeout float64 `toml:"http_write_timeout" env:"ETCD_HTTP_WRITE_TIMEOUT"`
|
||||||
MaxResultBuffer int `toml:"max_result_buffer" env:"ETCD_MAX_RESULT_BUFFER"`
|
InternalBinaryDir string `toml:"internal_binary_dir" env:"ETCD_INTERNAL_BINARY_DIR"`
|
||||||
MaxRetryAttempts int `toml:"max_retry_attempts" env:"ETCD_MAX_RETRY_ATTEMPTS"`
|
Peers []string `toml:"peers" env:"ETCD_PEERS"`
|
||||||
RetryInterval float64 `toml:"retry_interval" env:"ETCD_RETRY_INTERVAL"`
|
PeersFile string `toml:"peers_file" env:"ETCD_PEERS_FILE"`
|
||||||
Name string `toml:"name" env:"ETCD_NAME"`
|
MaxResultBuffer int `toml:"max_result_buffer" env:"ETCD_MAX_RESULT_BUFFER"`
|
||||||
Snapshot bool `toml:"snapshot" env:"ETCD_SNAPSHOT"`
|
MaxRetryAttempts int `toml:"max_retry_attempts" env:"ETCD_MAX_RETRY_ATTEMPTS"`
|
||||||
SnapshotCount int `toml:"snapshot_count" env:"ETCD_SNAPSHOTCOUNT"`
|
RetryInterval float64 `toml:"retry_interval" env:"ETCD_RETRY_INTERVAL"`
|
||||||
ShowHelp bool
|
Name string `toml:"name" env:"ETCD_NAME"`
|
||||||
ShowVersion bool
|
Snapshot bool `toml:"snapshot" env:"ETCD_SNAPSHOT"`
|
||||||
Verbose bool `toml:"verbose" env:"ETCD_VERBOSE"`
|
SnapshotCount int `toml:"snapshot_count" env:"ETCD_SNAPSHOTCOUNT"`
|
||||||
VeryVerbose bool `toml:"very_verbose" env:"ETCD_VERY_VERBOSE"`
|
ShowHelp bool
|
||||||
VeryVeryVerbose bool `toml:"very_very_verbose" env:"ETCD_VERY_VERY_VERBOSE"`
|
ShowVersion bool
|
||||||
Peer struct {
|
Verbose bool `toml:"verbose" env:"ETCD_VERBOSE"`
|
||||||
|
VeryVerbose bool `toml:"very_verbose" env:"ETCD_VERY_VERBOSE"`
|
||||||
|
VeryVeryVerbose bool `toml:"very_very_verbose" env:"ETCD_VERY_VERY_VERBOSE"`
|
||||||
|
Peer struct {
|
||||||
Addr string `toml:"addr" env:"ETCD_PEER_ADDR"`
|
Addr string `toml:"addr" env:"ETCD_PEER_ADDR"`
|
||||||
BindAddr string `toml:"bind_addr" env:"ETCD_PEER_BIND_ADDR"`
|
BindAddr string `toml:"bind_addr" env:"ETCD_PEER_BIND_ADDR"`
|
||||||
CAFile string `toml:"ca_file" env:"ETCD_PEER_CA_FILE"`
|
CAFile string `toml:"ca_file" env:"ETCD_PEER_CA_FILE"`
|
||||||
@ -83,7 +90,7 @@ type Config struct {
|
|||||||
HeartbeatInterval int `toml:"heartbeat_interval" env:"ETCD_PEER_HEARTBEAT_INTERVAL"`
|
HeartbeatInterval int `toml:"heartbeat_interval" env:"ETCD_PEER_HEARTBEAT_INTERVAL"`
|
||||||
ElectionTimeout int `toml:"election_timeout" env:"ETCD_PEER_ELECTION_TIMEOUT"`
|
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"`
|
GraphiteHost string `toml:"graphite_host" env:"ETCD_GRAPHITE_HOST"`
|
||||||
Cluster struct {
|
Cluster struct {
|
||||||
ActiveSize int `toml:"active_size" env:"ETCD_CLUSTER_ACTIVE_SIZE"`
|
ActiveSize int `toml:"active_size" env:"ETCD_CLUSTER_ACTIVE_SIZE"`
|
||||||
@ -97,6 +104,8 @@ func New() *Config {
|
|||||||
c := new(Config)
|
c := new(Config)
|
||||||
c.SystemPath = DefaultSystemConfigPath
|
c.SystemPath = DefaultSystemConfigPath
|
||||||
c.Addr = "127.0.0.1:4001"
|
c.Addr = "127.0.0.1:4001"
|
||||||
|
c.HTTPReadTimeout = server.DefaultReadTimeout
|
||||||
|
c.HTTPWriteTimeout = server.DefaultWriteTimeout
|
||||||
c.MaxResultBuffer = 1024
|
c.MaxResultBuffer = 1024
|
||||||
c.MaxRetryAttempts = 3
|
c.MaxRetryAttempts = 3
|
||||||
c.RetryInterval = 10.0
|
c.RetryInterval = 10.0
|
||||||
@ -111,6 +120,7 @@ func New() *Config {
|
|||||||
c.Cluster.ActiveSize = server.DefaultActiveSize
|
c.Cluster.ActiveSize = server.DefaultActiveSize
|
||||||
c.Cluster.RemoveDelay = server.DefaultRemoveDelay
|
c.Cluster.RemoveDelay = server.DefaultRemoveDelay
|
||||||
c.Cluster.SyncInterval = server.DefaultSyncInterval
|
c.Cluster.SyncInterval = server.DefaultSyncInterval
|
||||||
|
c.InternalBinaryDir = DefaultInternalBinaryDir
|
||||||
return c
|
return c
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -254,7 +264,11 @@ func (c *Config) LoadFlags(arguments []string) error {
|
|||||||
f.StringVar(&c.Peer.CertFile, "peer-cert-file", c.Peer.CertFile, "")
|
f.StringVar(&c.Peer.CertFile, "peer-cert-file", c.Peer.CertFile, "")
|
||||||
f.StringVar(&c.Peer.KeyFile, "peer-key-file", c.Peer.KeyFile, "")
|
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.StringVar(&c.DataDir, "data-dir", c.DataDir, "")
|
||||||
|
f.StringVar(&c.InternalBinaryDir, "internal-binary-dir", c.InternalBinaryDir, "")
|
||||||
f.IntVar(&c.MaxResultBuffer, "max-result-buffer", c.MaxResultBuffer, "")
|
f.IntVar(&c.MaxResultBuffer, "max-result-buffer", c.MaxResultBuffer, "")
|
||||||
f.IntVar(&c.MaxRetryAttempts, "max-retry-attempts", c.MaxRetryAttempts, "")
|
f.IntVar(&c.MaxRetryAttempts, "max-retry-attempts", c.MaxRetryAttempts, "")
|
||||||
f.Float64Var(&c.RetryInterval, "retry-interval", c.RetryInterval, "")
|
f.Float64Var(&c.RetryInterval, "retry-interval", c.RetryInterval, "")
|
||||||
@ -267,7 +281,7 @@ func (c *Config) LoadFlags(arguments []string) error {
|
|||||||
f.IntVar(&c.SnapshotCount, "snapshot-count", c.SnapshotCount, "")
|
f.IntVar(&c.SnapshotCount, "snapshot-count", c.SnapshotCount, "")
|
||||||
f.StringVar(&c.CPUProfileFile, "cpuprofile", "", "")
|
f.StringVar(&c.CPUProfileFile, "cpuprofile", "", "")
|
||||||
|
|
||||||
f.StringVar(&c.strTrace, "trace", "", "")
|
f.StringVar(&c.StrTrace, "trace", "", "")
|
||||||
f.StringVar(&c.GraphiteHost, "graphite-host", "", "")
|
f.StringVar(&c.GraphiteHost, "graphite-host", "", "")
|
||||||
|
|
||||||
f.IntVar(&c.Cluster.ActiveSize, "cluster-active-size", c.Cluster.ActiveSize, "")
|
f.IntVar(&c.Cluster.ActiveSize, "cluster-active-size", c.Cluster.ActiveSize, "")
|
||||||
@ -297,6 +311,8 @@ func (c *Config) LoadFlags(arguments []string) error {
|
|||||||
f.IntVar(&c.MaxRetryAttempts, "r", c.MaxRetryAttempts, "(deprecated)")
|
f.IntVar(&c.MaxRetryAttempts, "r", c.MaxRetryAttempts, "(deprecated)")
|
||||||
f.IntVar(&c.SnapshotCount, "snapshotCount", c.SnapshotCount, "(deprecated)")
|
f.IntVar(&c.SnapshotCount, "snapshotCount", c.SnapshotCount, "(deprecated)")
|
||||||
f.IntVar(&c.Peer.HeartbeatInterval, "peer-heartbeat-timeout", c.Peer.HeartbeatInterval, "(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
|
// END DEPRECATED FLAGS
|
||||||
|
|
||||||
if err := f.Parse(arguments); err != nil {
|
if err := f.Parse(arguments); err != nil {
|
||||||
@ -431,7 +447,7 @@ func (c *Config) MetricsBucketName() string {
|
|||||||
|
|
||||||
// Trace determines if any trace-level information should be emitted
|
// Trace determines if any trace-level information should be emitted
|
||||||
func (c *Config) Trace() bool {
|
func (c *Config) Trace() bool {
|
||||||
return c.strTrace == "*"
|
return c.StrTrace == "*"
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Config) ClusterConfig() *server.ClusterConfig {
|
func (c *Config) ClusterConfig() *server.ClusterConfig {
|
||||||
|
@ -19,6 +19,7 @@ func TestConfigTOML(t *testing.T) {
|
|||||||
cpu_profile_file = "XXX"
|
cpu_profile_file = "XXX"
|
||||||
data_dir = "/tmp/data"
|
data_dir = "/tmp/data"
|
||||||
discovery = "http://example.com/foobar"
|
discovery = "http://example.com/foobar"
|
||||||
|
internal_binary_dir = "/tmp/etcd/internal_versions"
|
||||||
key_file = "/tmp/file.key"
|
key_file = "/tmp/file.key"
|
||||||
bind_addr = "127.0.0.1:4003"
|
bind_addr = "127.0.0.1:4003"
|
||||||
peers = ["coreos.com:4001", "coreos.com:4002"]
|
peers = ["coreos.com:4001", "coreos.com:4002"]
|
||||||
@ -27,9 +28,11 @@ func TestConfigTOML(t *testing.T) {
|
|||||||
max_result_buffer = 512
|
max_result_buffer = 512
|
||||||
max_retry_attempts = 5
|
max_retry_attempts = 5
|
||||||
name = "test-name"
|
name = "test-name"
|
||||||
|
http_read_timeout = 2.34
|
||||||
snapshot = true
|
snapshot = true
|
||||||
verbose = true
|
verbose = true
|
||||||
very_verbose = true
|
very_verbose = true
|
||||||
|
http_write_timeout = 1.23
|
||||||
|
|
||||||
[peer]
|
[peer]
|
||||||
addr = "127.0.0.1:7002"
|
addr = "127.0.0.1:7002"
|
||||||
@ -52,6 +55,9 @@ func TestConfigTOML(t *testing.T) {
|
|||||||
assert.Equal(t, c.CorsOrigins, []string{"*"}, "")
|
assert.Equal(t, c.CorsOrigins, []string{"*"}, "")
|
||||||
assert.Equal(t, c.DataDir, "/tmp/data", "")
|
assert.Equal(t, c.DataDir, "/tmp/data", "")
|
||||||
assert.Equal(t, c.Discovery, "http://example.com/foobar", "")
|
assert.Equal(t, c.Discovery, "http://example.com/foobar", "")
|
||||||
|
assert.Equal(t, c.InternalBinaryDir, "/tmp/etcd/internal_versions", "")
|
||||||
|
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.KeyFile, "/tmp/file.key", "")
|
||||||
assert.Equal(t, c.BindAddr, "127.0.0.1:4003", "")
|
assert.Equal(t, c.BindAddr, "127.0.0.1:4003", "")
|
||||||
assert.Equal(t, c.Peers, []string{"coreos.com:4001", "coreos.com:4002"}, "")
|
assert.Equal(t, c.Peers, []string{"coreos.com:4001", "coreos.com:4002"}, "")
|
||||||
@ -80,6 +86,9 @@ func TestConfigEnv(t *testing.T) {
|
|||||||
os.Setenv("ETCD_CORS", "localhost:4001,localhost:4002")
|
os.Setenv("ETCD_CORS", "localhost:4001,localhost:4002")
|
||||||
os.Setenv("ETCD_DATA_DIR", "/tmp/data")
|
os.Setenv("ETCD_DATA_DIR", "/tmp/data")
|
||||||
os.Setenv("ETCD_DISCOVERY", "http://example.com/foobar")
|
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_INTERNAL_BINARY_DIR", "/tmp/etcd/internal_versions")
|
||||||
os.Setenv("ETCD_KEY_FILE", "/tmp/file.key")
|
os.Setenv("ETCD_KEY_FILE", "/tmp/file.key")
|
||||||
os.Setenv("ETCD_BIND_ADDR", "127.0.0.1:4003")
|
os.Setenv("ETCD_BIND_ADDR", "127.0.0.1:4003")
|
||||||
os.Setenv("ETCD_PEERS", "coreos.com:4001,coreos.com:4002")
|
os.Setenv("ETCD_PEERS", "coreos.com:4001,coreos.com:4002")
|
||||||
@ -107,6 +116,9 @@ func TestConfigEnv(t *testing.T) {
|
|||||||
assert.Equal(t, c.CorsOrigins, []string{"localhost:4001", "localhost:4002"}, "")
|
assert.Equal(t, c.CorsOrigins, []string{"localhost:4001", "localhost:4002"}, "")
|
||||||
assert.Equal(t, c.DataDir, "/tmp/data", "")
|
assert.Equal(t, c.DataDir, "/tmp/data", "")
|
||||||
assert.Equal(t, c.Discovery, "http://example.com/foobar", "")
|
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.InternalBinaryDir, "/tmp/etcd/internal_versions", "")
|
||||||
assert.Equal(t, c.KeyFile, "/tmp/file.key", "")
|
assert.Equal(t, c.KeyFile, "/tmp/file.key", "")
|
||||||
assert.Equal(t, c.BindAddr, "127.0.0.1:4003", "")
|
assert.Equal(t, c.BindAddr, "127.0.0.1:4003", "")
|
||||||
assert.Equal(t, c.Peers, []string{"coreos.com:4001", "coreos.com:4002"}, "")
|
assert.Equal(t, c.Peers, []string{"coreos.com:4001", "coreos.com:4002"}, "")
|
||||||
@ -553,19 +565,12 @@ func TestConfigClusterRemoveDelayFlag(t *testing.T) {
|
|||||||
assert.Equal(t, c.Cluster.RemoveDelay, 100.0, "")
|
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) {
|
func TestConfigClusterSyncIntervalFlag(t *testing.T) {
|
||||||
c := New()
|
c := New()
|
||||||
assert.Nil(t, c.LoadFlags([]string{"-cluster-sync-interval", "10"}), "")
|
assert.Nil(t, c.LoadFlags([]string{"-http-read-timeout", "2.34"}), "")
|
||||||
assert.Equal(t, c.Cluster.SyncInterval, 10.0, "")
|
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.
|
// Ensures that a system config field is overridden by a custom config field.
|
||||||
|
14
etcd/etcd.go
14
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())
|
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)
|
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)
|
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)
|
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 := sync.WaitGroup{}
|
||||||
wg.Add(2)
|
wg.Add(2)
|
||||||
@ -300,6 +309,7 @@ func (e *Etcd) runServer() {
|
|||||||
for {
|
for {
|
||||||
if e.mode == PeerMode {
|
if e.mode == PeerMode {
|
||||||
log.Infof("%v starting in peer mode", e.Config.Name)
|
log.Infof("%v starting in peer mode", e.Config.Name)
|
||||||
|
go registerAvailableInternalVersions(e.Config.InternalBinaryDir, e.Config.Name, e.Config.Addr, e.Config.EtcdTLSInfo())
|
||||||
// Starting peer server should be followed close by listening on its port
|
// Starting peer server should be followed close by listening on its port
|
||||||
// If not, it may leave many requests unaccepted, or cannot receive heartbeat from the cluster.
|
// If not, it may leave many requests unaccepted, or cannot receive heartbeat from the cluster.
|
||||||
// One severe problem caused if failing receiving heartbeats is when the second node joins one-node cluster,
|
// One severe problem caused if failing receiving heartbeats is when the second node joins one-node cluster,
|
||||||
|
48
etcd/upgrade.go
Normal file
48
etcd/upgrade.go
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
package etcd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/coreos/etcd/log"
|
||||||
|
"github.com/coreos/etcd/server"
|
||||||
|
"github.com/coreos/etcd/third_party/github.com/coreos/go-etcd/etcd"
|
||||||
|
)
|
||||||
|
|
||||||
|
func registerAvailableInternalVersions(internalBinaryDir, name, addr string, tls *server.TLSInfo) {
|
||||||
|
var c *etcd.Client
|
||||||
|
if tls.Scheme() == "http" {
|
||||||
|
c = etcd.NewClient([]string{addr})
|
||||||
|
} else {
|
||||||
|
var err error
|
||||||
|
c, err = etcd.NewTLSClient([]string{addr}, tls.CertFile, tls.KeyFile, tls.CAFile)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("client TLS error: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
vers, err := getInternalVersions(internalBinaryDir)
|
||||||
|
if err != nil {
|
||||||
|
log.Infof("failed to get local etcd versions: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for _, v := range vers {
|
||||||
|
for {
|
||||||
|
_, err := c.Set("/_etcd/available-internal-versions/"+v+"/"+name, "ok", 0)
|
||||||
|
if err == nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
time.Sleep(time.Second)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
log.Infof("%s: available_internal_versions %s is registered into key space successfully.", name, vers)
|
||||||
|
}
|
||||||
|
|
||||||
|
func getInternalVersions(internalBinaryDir string) ([]string, error) {
|
||||||
|
dir, err := os.Open(internalBinaryDir)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer dir.Close()
|
||||||
|
return dir.Readdirnames(-1)
|
||||||
|
}
|
@ -54,6 +54,7 @@ type CORSHandler struct {
|
|||||||
func (h *CORSHandler) addHeader(w http.ResponseWriter, origin string) {
|
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-Methods", "POST, GET, OPTIONS, PUT, DELETE")
|
||||||
w.Header().Add("Access-Control-Allow-Origin", origin)
|
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
|
// ServeHTTP adds the correct CORS headers based on the origin and returns immediately
|
||||||
|
@ -1,85 +0,0 @@
|
|||||||
package leader
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/coreos/etcd/server"
|
|
||||||
"github.com/coreos/etcd/tests"
|
|
||||||
"github.com/coreos/etcd/third_party/github.com/stretchr/testify/assert"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Ensure that a leader can be set and read.
|
|
||||||
func TestModLeaderSet(t *testing.T) {
|
|
||||||
tests.RunServer(func(s *server.Server) {
|
|
||||||
// Set leader.
|
|
||||||
body, status, err := testSetLeader(s, "foo", "xxx", 10)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
assert.Equal(t, status, 200)
|
|
||||||
assert.Equal(t, body, "2")
|
|
||||||
|
|
||||||
// Check that the leader is set.
|
|
||||||
body, status, err = testGetLeader(s, "foo")
|
|
||||||
assert.NoError(t, err)
|
|
||||||
assert.Equal(t, status, 200)
|
|
||||||
assert.Equal(t, body, "xxx")
|
|
||||||
|
|
||||||
// Delete leader.
|
|
||||||
body, status, err = testDeleteLeader(s, "foo", "xxx")
|
|
||||||
assert.NoError(t, err)
|
|
||||||
assert.Equal(t, status, 200)
|
|
||||||
assert.Equal(t, body, "")
|
|
||||||
|
|
||||||
// Check that the leader is removed.
|
|
||||||
body, status, err = testGetLeader(s, "foo")
|
|
||||||
assert.NoError(t, err)
|
|
||||||
assert.Equal(t, status, 200)
|
|
||||||
assert.Equal(t, body, "")
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ensure that a leader can be renewed.
|
|
||||||
func TestModLeaderRenew(t *testing.T) {
|
|
||||||
tests.RunServer(func(s *server.Server) {
|
|
||||||
// Set leader.
|
|
||||||
body, status, err := testSetLeader(s, "foo", "xxx", 2)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
assert.Equal(t, status, 200)
|
|
||||||
assert.Equal(t, body, "2")
|
|
||||||
|
|
||||||
time.Sleep(1 * time.Second)
|
|
||||||
|
|
||||||
// Renew leader.
|
|
||||||
body, status, err = testSetLeader(s, "foo", "xxx", 3)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
assert.Equal(t, status, 200)
|
|
||||||
assert.Equal(t, body, "2")
|
|
||||||
|
|
||||||
time.Sleep(2 * time.Second)
|
|
||||||
|
|
||||||
// Check that the leader is set.
|
|
||||||
body, status, err = testGetLeader(s, "foo")
|
|
||||||
assert.NoError(t, err)
|
|
||||||
assert.Equal(t, status, 200)
|
|
||||||
assert.Equal(t, body, "xxx")
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func testSetLeader(s *server.Server, key string, name string, ttl int) (string, int, error) {
|
|
||||||
resp, err := tests.PutForm(fmt.Sprintf("%s/mod/v2/leader/%s?name=%s&ttl=%d", s.URL(), key, name, ttl), nil)
|
|
||||||
ret := tests.ReadBody(resp)
|
|
||||||
return string(ret), resp.StatusCode, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func testGetLeader(s *server.Server, key string) (string, int, error) {
|
|
||||||
resp, err := tests.Get(fmt.Sprintf("%s/mod/v2/leader/%s", s.URL(), key))
|
|
||||||
ret := tests.ReadBody(resp)
|
|
||||||
return string(ret), resp.StatusCode, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func testDeleteLeader(s *server.Server, key string, name string) (string, int, error) {
|
|
||||||
resp, err := tests.DeleteForm(fmt.Sprintf("%s/mod/v2/leader/%s?name=%s", s.URL(), key, name), nil)
|
|
||||||
ret := tests.ReadBody(resp)
|
|
||||||
return string(ret), resp.StatusCode, err
|
|
||||||
}
|
|
@ -25,7 +25,6 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// ClusterConfig represents cluster-wide configuration settings.
|
// ClusterConfig represents cluster-wide configuration settings.
|
||||||
// These settings can only be changed through Raft.
|
|
||||||
type ClusterConfig struct {
|
type ClusterConfig struct {
|
||||||
// ActiveSize is the maximum number of node that can join as Raft followers.
|
// ActiveSize is the maximum number of node that can join as Raft followers.
|
||||||
// Nodes that join the cluster after the limit is reached are standbys.
|
// Nodes that join the cluster after the limit is reached are standbys.
|
||||||
|
@ -46,6 +46,7 @@ func (c *JoinCommand) NodeName() string {
|
|||||||
// applyJoin attempts to join a machine to the cluster.
|
// applyJoin attempts to join a machine to the cluster.
|
||||||
func applyJoin(c *JoinCommand, context raft.Context) (uint64, error) {
|
func applyJoin(c *JoinCommand, context raft.Context) (uint64, error) {
|
||||||
ps, _ := context.Server().Context().(*PeerServer)
|
ps, _ := context.Server().Context().(*PeerServer)
|
||||||
|
ps.raftServer.FlushCommitIndex()
|
||||||
commitIndex := context.CommitIndex()
|
commitIndex := context.CommitIndex()
|
||||||
|
|
||||||
// Make sure we're not getting a cached value from the registry.
|
// Make sure we're not getting a cached value from the registry.
|
||||||
|
@ -3,10 +3,16 @@ package server
|
|||||||
import (
|
import (
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"net"
|
"net"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/coreos/etcd/log"
|
"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
|
// TLSServerConfig generates tls configuration based on TLSInfo
|
||||||
// If any error happens, this function will call log.Fatal
|
// If any error happens, this function will call log.Fatal
|
||||||
func TLSServerConfig(info *TLSInfo) *tls.Config {
|
func TLSServerConfig(info *TLSInfo) *tls.Config {
|
||||||
|
@ -6,6 +6,7 @@ import (
|
|||||||
"math/rand"
|
"math/rand"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
"os"
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
@ -292,6 +293,7 @@ func (s *PeerServer) Start(snapshot bool, clusterConfig *ClusterConfig) error {
|
|||||||
s.startRoutine(s.monitorTimeoutThreshold)
|
s.startRoutine(s.monitorTimeoutThreshold)
|
||||||
s.startRoutine(s.monitorActiveSize)
|
s.startRoutine(s.monitorActiveSize)
|
||||||
s.startRoutine(s.monitorPeerActivity)
|
s.startRoutine(s.monitorPeerActivity)
|
||||||
|
s.startRoutine(s.monitorVersion)
|
||||||
|
|
||||||
// open the snapshot
|
// open the snapshot
|
||||||
if snapshot {
|
if snapshot {
|
||||||
@ -370,6 +372,7 @@ func (s *PeerServer) HTTPHandler() http.Handler {
|
|||||||
router.HandleFunc("/v2/admin/machines", s.getMachinesHttpHandler).Methods("GET")
|
router.HandleFunc("/v2/admin/machines", s.getMachinesHttpHandler).Methods("GET")
|
||||||
router.HandleFunc("/v2/admin/machines/{name}", s.getMachineHttpHandler).Methods("GET")
|
router.HandleFunc("/v2/admin/machines/{name}", s.getMachineHttpHandler).Methods("GET")
|
||||||
router.HandleFunc("/v2/admin/machines/{name}", s.RemoveHttpHandler).Methods("DELETE")
|
router.HandleFunc("/v2/admin/machines/{name}", s.RemoveHttpHandler).Methods("DELETE")
|
||||||
|
router.HandleFunc("/v2/admin/next-internal-version", s.NextInternalVersionHandler).Methods("GET")
|
||||||
|
|
||||||
return router
|
return router
|
||||||
}
|
}
|
||||||
@ -772,9 +775,9 @@ func (s *PeerServer) startRoutine(f func()) {
|
|||||||
func (s *PeerServer) monitorSnapshot() {
|
func (s *PeerServer) monitorSnapshot() {
|
||||||
for {
|
for {
|
||||||
timer := time.NewTimer(s.snapConf.checkingInterval)
|
timer := time.NewTimer(s.snapConf.checkingInterval)
|
||||||
defer timer.Stop()
|
|
||||||
select {
|
select {
|
||||||
case <-s.closeChan:
|
case <-s.closeChan:
|
||||||
|
timer.Stop()
|
||||||
return
|
return
|
||||||
case <-timer.C:
|
case <-timer.C:
|
||||||
}
|
}
|
||||||
@ -807,6 +810,8 @@ func (s *PeerServer) monitorSync() {
|
|||||||
// monitorTimeoutThreshold groups timeout threshold events together and prints
|
// monitorTimeoutThreshold groups timeout threshold events together and prints
|
||||||
// them as a single log line.
|
// them as a single log line.
|
||||||
func (s *PeerServer) monitorTimeoutThreshold() {
|
func (s *PeerServer) monitorTimeoutThreshold() {
|
||||||
|
ticker := time.NewTicker(ThresholdMonitorTimeout)
|
||||||
|
defer ticker.Stop()
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case <-s.closeChan:
|
case <-s.closeChan:
|
||||||
@ -815,12 +820,10 @@ func (s *PeerServer) monitorTimeoutThreshold() {
|
|||||||
log.Infof("%s: warning: heartbeat near election timeout: %v", s.Config.Name, value)
|
log.Infof("%s: warning: heartbeat near election timeout: %v", s.Config.Name, value)
|
||||||
}
|
}
|
||||||
|
|
||||||
timer := time.NewTimer(ThresholdMonitorTimeout)
|
|
||||||
defer timer.Stop()
|
|
||||||
select {
|
select {
|
||||||
case <-s.closeChan:
|
case <-s.closeChan:
|
||||||
return
|
return
|
||||||
case <-timer.C:
|
case <-ticker.C:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -828,13 +831,13 @@ func (s *PeerServer) monitorTimeoutThreshold() {
|
|||||||
// monitorActiveSize has the leader periodically check the status of cluster
|
// monitorActiveSize has the leader periodically check the status of cluster
|
||||||
// nodes and swaps them out for standbys as needed.
|
// nodes and swaps them out for standbys as needed.
|
||||||
func (s *PeerServer) monitorActiveSize() {
|
func (s *PeerServer) monitorActiveSize() {
|
||||||
|
ticker := time.NewTicker(ActiveMonitorTimeout)
|
||||||
|
defer ticker.Stop()
|
||||||
for {
|
for {
|
||||||
timer := time.NewTimer(ActiveMonitorTimeout)
|
|
||||||
defer timer.Stop()
|
|
||||||
select {
|
select {
|
||||||
case <-s.closeChan:
|
case <-s.closeChan:
|
||||||
return
|
return
|
||||||
case <-timer.C:
|
case <-ticker.C:
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ignore while this peer is not a leader.
|
// Ignore while this peer is not a leader.
|
||||||
@ -853,7 +856,7 @@ func (s *PeerServer) monitorActiveSize() {
|
|||||||
// If we have more active nodes than we should then remove.
|
// If we have more active nodes than we should then remove.
|
||||||
if peerCount > activeSize {
|
if peerCount > activeSize {
|
||||||
peer := peers[rand.Intn(len(peers))]
|
peer := peers[rand.Intn(len(peers))]
|
||||||
log.Infof("%s: removing: %v", s.Config.Name, peer)
|
log.Infof("%s: removing node: %v; peer number %d > expected size %d", s.Config.Name, peer, peerCount, activeSize)
|
||||||
if _, err := s.raftServer.Do(&RemoveCommand{Name: peer}); err != nil {
|
if _, err := s.raftServer.Do(&RemoveCommand{Name: peer}); err != nil {
|
||||||
log.Infof("%s: warning: remove error: %v", s.Config.Name, err)
|
log.Infof("%s: warning: remove error: %v", s.Config.Name, err)
|
||||||
}
|
}
|
||||||
@ -864,13 +867,13 @@ func (s *PeerServer) monitorActiveSize() {
|
|||||||
|
|
||||||
// monitorPeerActivity has the leader periodically for dead nodes and demotes them.
|
// monitorPeerActivity has the leader periodically for dead nodes and demotes them.
|
||||||
func (s *PeerServer) monitorPeerActivity() {
|
func (s *PeerServer) monitorPeerActivity() {
|
||||||
|
ticker := time.NewTicker(PeerActivityMonitorTimeout)
|
||||||
|
defer ticker.Stop()
|
||||||
for {
|
for {
|
||||||
timer := time.NewTimer(PeerActivityMonitorTimeout)
|
|
||||||
defer timer.Stop()
|
|
||||||
select {
|
select {
|
||||||
case <-s.closeChan:
|
case <-s.closeChan:
|
||||||
return
|
return
|
||||||
case <-timer.C:
|
case <-ticker.C:
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ignore while this peer is not a leader.
|
// Ignore while this peer is not a leader.
|
||||||
@ -895,3 +898,30 @@ func (s *PeerServer) monitorPeerActivity() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *PeerServer) monitorVersion() {
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-s.closeChan:
|
||||||
|
return
|
||||||
|
case <-time.After(time.Second):
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := s.store.Get("/_etcd/next-internal-version", false, false)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// only support upgrading to etcd2
|
||||||
|
if *resp.Node.Value == "2" {
|
||||||
|
log.Infof("%s: detected next internal version 2, exit after 10 seconds.", s.Config.Name)
|
||||||
|
} else {
|
||||||
|
log.Infof("%s: detected invaild next internal version %s", s.Config.Name, *resp.Node.Value)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
time.Sleep(10 * time.Second)
|
||||||
|
// be nice to raft. try not to corrupt log file.
|
||||||
|
go s.raftServer.Stop()
|
||||||
|
time.Sleep(time.Second)
|
||||||
|
os.Exit(0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -3,6 +3,7 @@ package server
|
|||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"path"
|
||||||
"strconv"
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@ -188,6 +189,7 @@ func (ps *PeerServer) RemoveHttpHandler(w http.ResponseWriter, req *http.Request
|
|||||||
|
|
||||||
// Returns a JSON-encoded cluster configuration.
|
// Returns a JSON-encoded cluster configuration.
|
||||||
func (ps *PeerServer) getClusterConfigHttpHandler(w http.ResponseWriter, req *http.Request) {
|
func (ps *PeerServer) getClusterConfigHttpHandler(w http.ResponseWriter, req *http.Request) {
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
json.NewEncoder(w).Encode(ps.ClusterConfig())
|
json.NewEncoder(w).Encode(ps.ClusterConfig())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -217,6 +219,7 @@ func (ps *PeerServer) setClusterConfigHttpHandler(w http.ResponseWriter, req *ht
|
|||||||
log.Debugf("[recv] Update Cluster Config Request")
|
log.Debugf("[recv] Update Cluster Config Request")
|
||||||
ps.server.Dispatch(c, w, req)
|
ps.server.Dispatch(c, w, req)
|
||||||
|
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
json.NewEncoder(w).Encode(ps.ClusterConfig())
|
json.NewEncoder(w).Encode(ps.ClusterConfig())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -230,6 +233,7 @@ func (ps *PeerServer) getMachinesHttpHandler(w http.ResponseWriter, req *http.Re
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
json.NewEncoder(w).Encode(&machines)
|
json.NewEncoder(w).Encode(&machines)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -237,6 +241,7 @@ func (ps *PeerServer) getMachinesHttpHandler(w http.ResponseWriter, req *http.Re
|
|||||||
func (ps *PeerServer) getMachineHttpHandler(w http.ResponseWriter, req *http.Request) {
|
func (ps *PeerServer) getMachineHttpHandler(w http.ResponseWriter, req *http.Request) {
|
||||||
vars := mux.Vars(req)
|
vars := mux.Vars(req)
|
||||||
m := ps.getMachineMessage(vars["name"], ps.raftServer.Leader())
|
m := ps.getMachineMessage(vars["name"], ps.raftServer.Leader())
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
json.NewEncoder(w).Encode(m)
|
json.NewEncoder(w).Encode(m)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -305,6 +310,48 @@ func (ps *PeerServer) UpgradeHttpHandler(w http.ResponseWriter, req *http.Reques
|
|||||||
w.WriteHeader(http.StatusOK)
|
w.WriteHeader(http.StatusOK)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (ps *PeerServer) NextInternalVersionHandler(w http.ResponseWriter, req *http.Request) {
|
||||||
|
for i := 0; i < 50; i++ {
|
||||||
|
if ps.raftServer.State() != raft.Leader {
|
||||||
|
l := ps.raftServer.Leader()
|
||||||
|
if l == "" {
|
||||||
|
time.Sleep(5 * time.Second)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
url, _ := ps.registry.PeerURL(l)
|
||||||
|
uhttp.Redirect(url, w, req)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
resp, err := ps.store.Get("/_etcd/available-internal-versions/2", true, true)
|
||||||
|
if err != nil {
|
||||||
|
time.Sleep(5 * time.Second)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
available := make(map[string]bool)
|
||||||
|
for _, n := range resp.Node.Nodes {
|
||||||
|
available[path.Base(n.Key)] = true
|
||||||
|
}
|
||||||
|
|
||||||
|
notfound := false
|
||||||
|
for _, n := range ps.registry.Names() {
|
||||||
|
if !available[n] {
|
||||||
|
notfound = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if notfound {
|
||||||
|
time.Sleep(5 * time.Second)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
c := ps.store.CommandFactory().CreateSetCommand("/_etcd/next-internal-version", false, "2", store.Permanent)
|
||||||
|
_, err = ps.raftServer.Do(c)
|
||||||
|
if err == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
w.WriteHeader(http.StatusServiceUnavailable)
|
||||||
|
}
|
||||||
|
|
||||||
// machineMessage represents information about a peer or standby in the registry.
|
// machineMessage represents information about a peer or standby in the registry.
|
||||||
type machineMessage struct {
|
type machineMessage struct {
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
|
@ -1,3 +0,0 @@
|
|||||||
package server
|
|
||||||
|
|
||||||
const ReleaseVersion = "0.4.3"
|
|
@ -285,7 +285,7 @@ func (s *Server) Dispatch(c raft.Command, w http.ResponseWriter, req *http.Reque
|
|||||||
// Handler to return the current version of etcd.
|
// Handler to return the current version of etcd.
|
||||||
func (s *Server) GetVersionHandler(w http.ResponseWriter, req *http.Request) error {
|
func (s *Server) GetVersionHandler(w http.ResponseWriter, req *http.Request) error {
|
||||||
w.WriteHeader(http.StatusOK)
|
w.WriteHeader(http.StatusOK)
|
||||||
fmt.Fprintf(w, "etcd %s", ReleaseVersion)
|
fmt.Fprintf(w, `{"releaseVersion":"%s","internalVersion":"%s"}`, ReleaseVersion, InternalVersion)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -311,6 +311,7 @@ func (s *Server) GetPeersHandler(w http.ResponseWriter, req *http.Request) error
|
|||||||
|
|
||||||
// Retrieves stats on the Raft server.
|
// Retrieves stats on the Raft server.
|
||||||
func (s *Server) GetStatsHandler(w http.ResponseWriter, req *http.Request) error {
|
func (s *Server) GetStatsHandler(w http.ResponseWriter, req *http.Request) error {
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
w.Write(s.peerServer.Stats())
|
w.Write(s.peerServer.Stats())
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -318,21 +319,19 @@ func (s *Server) GetStatsHandler(w http.ResponseWriter, req *http.Request) error
|
|||||||
// Retrieves stats on the leader.
|
// Retrieves stats on the leader.
|
||||||
func (s *Server) GetLeaderStatsHandler(w http.ResponseWriter, req *http.Request) error {
|
func (s *Server) GetLeaderStatsHandler(w http.ResponseWriter, req *http.Request) error {
|
||||||
if s.peerServer.RaftServer().State() == raft.Leader {
|
if s.peerServer.RaftServer().State() == raft.Leader {
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
w.Write(s.peerServer.PeerStats())
|
w.Write(s.peerServer.PeerStats())
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
leader := s.peerServer.RaftServer().Leader()
|
w.WriteHeader(http.StatusForbidden)
|
||||||
if leader == "" {
|
w.Write([]byte("not current leader"))
|
||||||
return etcdErr.NewError(300, "", s.Store().Index())
|
|
||||||
}
|
|
||||||
hostname, _ := s.registry.ClientURL(leader)
|
|
||||||
uhttp.Redirect(hostname, w, req)
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Retrieves stats on the leader.
|
// Retrieves stats on the leader.
|
||||||
func (s *Server) GetStoreStatsHandler(w http.ResponseWriter, req *http.Request) error {
|
func (s *Server) GetStoreStatsHandler(w http.ResponseWriter, req *http.Request) error {
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
w.Write(s.store.JsonStats())
|
w.Write(s.store.JsonStats())
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -30,6 +30,7 @@ type StandbyServerConfig struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type standbyInfo struct {
|
type standbyInfo struct {
|
||||||
|
// stay running in standby mode
|
||||||
Running bool
|
Running bool
|
||||||
Cluster []*machineMessage
|
Cluster []*machineMessage
|
||||||
SyncInterval float64
|
SyncInterval float64
|
||||||
@ -78,12 +79,16 @@ func (s *StandbyServer) Start() {
|
|||||||
s.removeNotify = make(chan bool)
|
s.removeNotify = make(chan bool)
|
||||||
s.closeChan = 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)
|
s.routineGroup.Add(1)
|
||||||
go func() {
|
go func() {
|
||||||
defer s.routineGroup.Done()
|
defer s.routineGroup.Done()
|
||||||
s.monitorCluster()
|
s.monitorCluster()
|
||||||
}()
|
}()
|
||||||
s.Running = true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Stop stops the server gracefully.
|
// Stop stops the server gracefully.
|
||||||
@ -97,11 +102,6 @@ func (s *StandbyServer) Stop() {
|
|||||||
|
|
||||||
close(s.closeChan)
|
close(s.closeChan)
|
||||||
s.routineGroup.Wait()
|
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
|
// RemoveNotify notifies the server is removed from standby mode and ready
|
||||||
@ -178,13 +178,21 @@ func (s *StandbyServer) redirectRequests(w http.ResponseWriter, r *http.Request)
|
|||||||
// monitorCluster assumes that the machine has tried to join the cluster and
|
// monitorCluster assumes that the machine has tried to join the cluster and
|
||||||
// failed, so it waits for the interval at the beginning.
|
// failed, so it waits for the interval at the beginning.
|
||||||
func (s *StandbyServer) monitorCluster() {
|
func (s *StandbyServer) monitorCluster() {
|
||||||
|
ticker := time.NewTicker(time.Duration(int64(s.SyncInterval * float64(time.Second))))
|
||||||
|
defer ticker.Stop()
|
||||||
for {
|
for {
|
||||||
timer := time.NewTimer(time.Duration(int64(s.SyncInterval * float64(time.Second))))
|
|
||||||
defer timer.Stop()
|
|
||||||
select {
|
select {
|
||||||
case <-s.closeChan:
|
case <-s.closeChan:
|
||||||
return
|
return
|
||||||
case <-timer.C:
|
case <-ticker.C:
|
||||||
|
}
|
||||||
|
|
||||||
|
ok, err := s.checkMemberInternalVersionIsV2()
|
||||||
|
if err != nil {
|
||||||
|
log.Warnf("fail checking internal version(%v): %v", s.ClusterURLs(), err)
|
||||||
|
} else if ok {
|
||||||
|
log.Infof("Detect the cluster has been upgraded to v2. Exit now.")
|
||||||
|
os.Exit(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := s.syncCluster(nil); err != nil {
|
if err := s.syncCluster(nil); err != nil {
|
||||||
@ -204,6 +212,10 @@ func (s *StandbyServer) monitorCluster() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
log.Infof("join through leader %v", leader.PeerURL)
|
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() {
|
go func() {
|
||||||
s.Stop()
|
s.Stop()
|
||||||
close(s.removeNotify)
|
close(s.removeNotify)
|
||||||
@ -212,6 +224,39 @@ func (s *StandbyServer) monitorCluster() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *StandbyServer) checkMemberInternalVersionIsV2() (bool, error) {
|
||||||
|
c := &http.Client{Transport: s.client.Client.Transport}
|
||||||
|
for _, memb := range s.Cluster {
|
||||||
|
url := memb.ClientURL
|
||||||
|
resp, err := c.Get(url + "/version")
|
||||||
|
if err != nil {
|
||||||
|
log.Debugf("failed to get /version from %s", url)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
b, err := ioutil.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
log.Debugf("failed to read body from %s", url)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
var m map[string]string
|
||||||
|
err = json.Unmarshal(b, &m)
|
||||||
|
if err != nil {
|
||||||
|
log.Debugf("failed to unmarshal body %s from %s", b, url)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
switch m["internalVersion"] {
|
||||||
|
case "1":
|
||||||
|
return false, nil
|
||||||
|
case "2":
|
||||||
|
return true, nil
|
||||||
|
default:
|
||||||
|
log.Warnf("unrecognized internal version %s from %s", m["internalVersion"], url)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false, fmt.Errorf("failed to get version")
|
||||||
|
}
|
||||||
|
|
||||||
func (s *StandbyServer) syncCluster(peerURLs []string) error {
|
func (s *StandbyServer) syncCluster(peerURLs []string) error {
|
||||||
peerURLs = append(s.ClusterURLs(), peerURLs...)
|
peerURLs = append(s.ClusterURLs(), peerURLs...)
|
||||||
|
|
||||||
|
@ -15,15 +15,16 @@ Usage:
|
|||||||
etcd -version
|
etcd -version
|
||||||
|
|
||||||
Options:
|
Options:
|
||||||
-h -help Show this screen.
|
-h -help Show this screen.
|
||||||
--version Show version.
|
--version Show version.
|
||||||
-f -force Force a new configuration to be used.
|
-f -force Force a new configuration to be used.
|
||||||
-config=<path> Path to configuration file.
|
-config=<path> Path to configuration file.
|
||||||
-name=<name> Name of this node in the etcd cluster.
|
-name=<name> Name of this node in the etcd cluster.
|
||||||
-data-dir=<path> Path to the data directory.
|
-data-dir=<path> Path to the data directory.
|
||||||
-cors=<origins> Comma-separated list of CORS origins.
|
-internal-binary-dir=<path> Path to the etcd internal binary directory.
|
||||||
-v Enabled verbose logging.
|
-cors=<origins> Comma-separated list of CORS origins.
|
||||||
-vv Enabled very verbose logging.
|
-v Enabled verbose logging.
|
||||||
|
-vv Enabled very verbose logging.
|
||||||
|
|
||||||
Cluster Configuration Options:
|
Cluster Configuration Options:
|
||||||
-discovery=<url> Discovery service used to find a peer list.
|
-discovery=<url> Discovery service used to find a peer list.
|
||||||
@ -53,9 +54,11 @@ Other Options:
|
|||||||
-max-result-buffer Max size of the result buffer.
|
-max-result-buffer Max size of the result buffer.
|
||||||
-max-retry-attempts Number of times a node will try to join a cluster.
|
-max-retry-attempts Number of times a node will try to join a cluster.
|
||||||
-retry-interval Seconds to wait between cluster join retry attempts.
|
-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=false Disable log snapshots
|
||||||
-snapshot-count Number of transactions before issuing a snapshot.
|
-snapshot-count Number of transactions before issuing a snapshot.
|
||||||
|
-cluster-active-size Number of active nodes in the cluster.
|
||||||
|
-cluster-remove-delay Seconds before one node is removed.
|
||||||
|
-cluster-sync-interval Seconds between synchronizations for standby mode.
|
||||||
`
|
`
|
||||||
|
|
||||||
// Usage returns the usage message for etcd.
|
// Usage returns the usage message for etcd.
|
||||||
|
@ -17,6 +17,14 @@ func GetHandler(w http.ResponseWriter, req *http.Request, s Server) error {
|
|||||||
vars := mux.Vars(req)
|
vars := mux.Vars(req)
|
||||||
key := "/" + vars["key"]
|
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
|
// Help client to redirect the request to the current leader
|
||||||
if req.FormValue("consistent") == "true" && s.State() != raft.Leader {
|
if req.FormValue("consistent") == "true" && s.State() != raft.Leader {
|
||||||
leader := s.Leader()
|
leader := s.Leader()
|
||||||
@ -35,8 +43,6 @@ func GetHandler(w http.ResponseWriter, req *http.Request, s Server) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
recursive := (req.FormValue("recursive") == "true")
|
|
||||||
sort := (req.FormValue("sorted") == "true")
|
|
||||||
waitIndex := req.FormValue("waitIndex")
|
waitIndex := req.FormValue("waitIndex")
|
||||||
stream := (req.FormValue("stream") == "true")
|
stream := (req.FormValue("stream") == "true")
|
||||||
|
|
||||||
@ -68,6 +74,7 @@ func handleWatch(key string, recursive, stream bool, waitIndex string, w http.Re
|
|||||||
closeChan := cn.CloseNotify()
|
closeChan := cn.CloseNotify()
|
||||||
|
|
||||||
writeHeaders(w, s)
|
writeHeaders(w, s)
|
||||||
|
w.(http.Flusher).Flush()
|
||||||
|
|
||||||
if stream {
|
if stream {
|
||||||
// watcher hub will not help to remove stream watcher
|
// watcher hub will not help to remove stream watcher
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
package server
|
package server
|
||||||
|
|
||||||
|
const ReleaseVersion = "0.4.8"
|
||||||
|
const InternalVersion = "1"
|
||||||
const Version = "v2"
|
const Version = "v2"
|
||||||
|
@ -24,6 +24,7 @@ type CommandFactory interface {
|
|||||||
prevIndex uint64, expireTime time.Time) raft.Command
|
prevIndex uint64, expireTime time.Time) raft.Command
|
||||||
CreateCompareAndDeleteCommand(key string, prevValue string, prevIndex uint64) raft.Command
|
CreateCompareAndDeleteCommand(key string, prevValue string, prevIndex uint64) raft.Command
|
||||||
CreateSyncCommand(now time.Time) 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.
|
// 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(),
|
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
|
||||||
|
}
|
@ -25,6 +25,7 @@ func TestClusterConfigSet(t *testing.T) {
|
|||||||
resp, _ = tests.Get("http://localhost:7002/v2/admin/config")
|
resp, _ = tests.Get("http://localhost:7002/v2/admin/config")
|
||||||
body := tests.ReadBodyJSON(resp)
|
body := tests.ReadBodyJSON(resp)
|
||||||
assert.Equal(t, resp.StatusCode, 200)
|
assert.Equal(t, resp.StatusCode, 200)
|
||||||
|
assert.Equal(t, resp.Header.Get("Content-Type"), "application/json")
|
||||||
assert.Equal(t, body["activeSize"], 3)
|
assert.Equal(t, body["activeSize"], 3)
|
||||||
assert.Equal(t, body["removeDelay"], 60)
|
assert.Equal(t, body["removeDelay"], 60)
|
||||||
}
|
}
|
||||||
@ -44,6 +45,7 @@ func TestClusterConfigReload(t *testing.T) {
|
|||||||
resp, _ = tests.Get("http://localhost:7002/v2/admin/config")
|
resp, _ = tests.Get("http://localhost:7002/v2/admin/config")
|
||||||
body := tests.ReadBodyJSON(resp)
|
body := tests.ReadBodyJSON(resp)
|
||||||
assert.Equal(t, resp.StatusCode, 200)
|
assert.Equal(t, resp.StatusCode, 200)
|
||||||
|
assert.Equal(t, resp.Header.Get("Content-Type"), "application/json")
|
||||||
assert.Equal(t, body["activeSize"], 3)
|
assert.Equal(t, body["activeSize"], 3)
|
||||||
assert.Equal(t, body["removeDelay"], 60)
|
assert.Equal(t, body["removeDelay"], 60)
|
||||||
|
|
||||||
@ -59,6 +61,7 @@ func TestClusterConfigReload(t *testing.T) {
|
|||||||
resp, _ = tests.Get("http://localhost:7002/v2/admin/config")
|
resp, _ = tests.Get("http://localhost:7002/v2/admin/config")
|
||||||
body = tests.ReadBodyJSON(resp)
|
body = tests.ReadBodyJSON(resp)
|
||||||
assert.Equal(t, resp.StatusCode, 200)
|
assert.Equal(t, resp.StatusCode, 200)
|
||||||
|
assert.Equal(t, resp.Header.Get("Content-Type"), "application/json")
|
||||||
assert.Equal(t, body["activeSize"], 3)
|
assert.Equal(t, body["activeSize"], 3)
|
||||||
assert.Equal(t, body["removeDelay"], 60)
|
assert.Equal(t, body["removeDelay"], 60)
|
||||||
}
|
}
|
||||||
@ -76,6 +79,7 @@ func TestGetMachines(t *testing.T) {
|
|||||||
t.FailNow()
|
t.FailNow()
|
||||||
}
|
}
|
||||||
assert.Equal(t, resp.StatusCode, 200)
|
assert.Equal(t, resp.StatusCode, 200)
|
||||||
|
assert.Equal(t, resp.Header.Get("Content-Type"), "application/json")
|
||||||
machines := make([]map[string]interface{}, 0)
|
machines := make([]map[string]interface{}, 0)
|
||||||
b := tests.ReadBody(resp)
|
b := tests.ReadBody(resp)
|
||||||
json.Unmarshal(b, &machines)
|
json.Unmarshal(b, &machines)
|
||||||
|
Reference in New Issue
Block a user