Merge remote-tracking branch 'upstream/master' into feature-parametric-timeout

Conflicts:
	Dockerfile
	server/usage.go
	tests/server_utils.go
This commit is contained in:
Neil Dunbar 2013-12-06 10:13:33 +00:00
commit a06f5e74af
21 changed files with 872 additions and 130 deletions

View File

@ -7,4 +7,5 @@ RUN apt-get install -y golang
ADD . /opt/etcd
RUN cd /opt/etcd && ./build
EXPOSE 4001 7001
ENTRYPOINT ["/opt/etcd/etcd", "-addr", "0.0.0.0:4001", "-bind-addr", "0.0.0.0"]
ENTRYPOINT ["/opt/etcd/etcd"]

270
README.md
View File

@ -83,7 +83,15 @@ curl -L http://127.0.0.1:4001/v2/keys/message -X PUT -d value="Hello world"
```
```json
{"action":"set","key":"/message","value":"Hello world","modifiedIndex":2}
{
"action": "set",
"node": {
"createdIndex": 2,
"key": "/message",
"modifiedIndex": 2,
"value": "Hello world"
}
}
```
This response contains four fields.
@ -112,7 +120,15 @@ curl -L http://127.0.0.1:4001/v2/keys/message
```
```json
{"action":"get","key":"/message","value":"Hello world","modifiedIndex":2}
{
"action": "get",
"node": {
"createdIndex": 2,
"key": "/message",
"modifiedIndex": 2,
"value": "Hello world"
}
}
```
@ -125,10 +141,19 @@ curl -L http://127.0.0.1:4001/v2/keys/message -XPUT -d value="Hello etcd"
```
```json
{"action":"set","key":"/message","prevValue":"Hello world","value":"Hello etcd","index":3}
{
"action": "set",
"node": {
"createdIndex": 3,
"key": "/message",
"modifiedIndex": 3,
"prevValue": "Hello world",
"value": "Hello etcd"
}
}
```
Notice that the `prevValue` is set to the previous value of the key - `Hello world`.
Notice that `node.prevValue` is set to the previous value of the key - `Hello world`.
It is useful when you want to atomically set a value to a key and get its old value.
@ -141,7 +166,15 @@ curl -L http://127.0.0.1:4001/v2/keys/message -XDELETE
```
```json
{"action":"delete","key":"/message","prevValue":"Hello etcd","modifiedIndex":4}
{
"action": "delete",
"node": {
"createdIndex": 3,
"key": "/message",
"modifiedIndex": 4,
"prevValue": "Hello etcd"
}
}
```
@ -155,7 +188,17 @@ curl -L http://127.0.0.1:4001/v2/keys/foo -XPUT -d value=bar -d ttl=5
```
```json
{"action":"set","key":"/foo","value":"bar","expiration":"2013-11-12T20:21:22.629352334-05:00","ttl":5,"modifiedIndex":5}
{
"action": "set",
"node": {
"createdIndex": 5,
"expiration": "2013-12-04T12:01:21.874888581-08:00",
"key": "/foo",
"modifiedIndex": 5,
"ttl": 5,
"value": "bar"
}
}
```
Note the two new fields in response:
@ -175,7 +218,12 @@ curl -L http://127.0.0.1:4001/v2/keys/foo
If the TTL has expired, the key will be deleted, and you will be returned a 100.
```json
{"errorCode":100,"message":"Key Not Found","cause":"/foo","index":6}
{
"cause": "/foo",
"errorCode": 100,
"index": 6,
"message": "Key Not Found"
}
```
@ -201,7 +249,15 @@ curl -L http://127.0.0.1:4001/v2/keys/foo -XPUT -d value=bar
The first terminal should get the notification and return with the same response as the set request.
```json
{"action":"set","key":"/foo","value":"bar","modifiedIndex":7}
{
"action": "set",
"node": {
"createdIndex": 7,
"key": "/foo",
"modifiedIndex": 7,
"value": "bar"
}
}
```
However, the watch command can do more than this.
@ -248,7 +304,12 @@ curl -L http://127.0.0.1:4001/v2/keys/foo?prevExist=false -XPUT -d value=three
The error code explains the problem:
```json
{"errorCode":105,"message":"Already exists","cause":"/foo","index":39776}
{
"cause": "/foo",
"errorCode": 105,
"index": 39776,
"message": "Already exists"
}
```
Now lets provide a `prevValue` parameter:
@ -260,7 +321,12 @@ curl -L http://127.0.0.1:4001/v2/keys/foo?prevValue=two -XPUT -d value=three
This will try to compare the previous value of the key and the previous value we provided. If they are equal, the value of the key will change to three.
```json
{"errorCode":101,"message":"Test Failed","cause":"[two != one] [0 != 8]","index":8}
{
"cause": "[two != one] [0 != 8]",
"errorCode": 101,
"index": 8,
"message": "Test Failed"
}
```
which means `CompareAndSwap` failed.
@ -274,10 +340,19 @@ curl -L http://127.0.0.1:4001/v2/keys/foo?prevValue=one -XPUT -d value=two
The response should be
```json
{"action":"compareAndSwap","key":"/foo","prevValue":"one","value":"two","modifiedIndex":9}
{
"action": "compareAndSwap",
"node": {
"createdIndex": 8,
"key": "/foo",
"modifiedIndex": 9,
"prevValue": "one",
"value": "two"
}
}
```
We successfully changed the value from “one” to “two” since we gave the correct previous value.
We successfully changed the value from "one" to "two" since we gave the correct previous value.
### Listing a directory
@ -295,7 +370,15 @@ curl -L http://127.0.0.1:4001/v2/keys/foo_dir/foo -XPUT -d value=bar
```
```json
{"action":"set","key":"/foo_dir/foo","value":"bar","modifiedIndex":10}
{
"action": "set",
"node": {
"createdIndex": 2,
"key": "/foo_dir/foo",
"modifiedIndex": 2,
"value": "bar"
}
}
```
Now we can list the keys under root `/`:
@ -307,7 +390,21 @@ curl -L http://127.0.0.1:4001/v2/keys/
We should see the response as an array of items:
```json
{"action":"get","key":"/","dir":true,"kvs":[{"key":"/foo","value":"two","modifiedIndex":9},{"key":"/foo_dir","dir":true,"modifiedIndex":10}],"modifiedIndex":0}
{
"action": "get",
"node": {
"dir": true,
"key": "/",
"nodes": [
{
"createdIndex": 2,
"dir": true,
"key": "/foo_dir",
"modifiedIndex": 2
}
]
}
}
```
Here we can see `/foo` is a key-value pair under `/` and `/foo_dir` is a directory.
@ -318,7 +415,29 @@ curl -L http://127.0.0.1:4001/v2/keys/?recursive=true
```
```json
{"action":"get","key":"/","dir":true,"kvs":[{"key":"/foo","value":"two","modifiedIndex":9},{"key":"/foo_dir","dir":true,"kvs":[{"key":"/foo_dir/foo","value":"bar","modifiedIndex":10}],"modifiedIndex":10}],"modifiedIndex":0}
{
"action": "get",
"node": {
"dir": true,
"key": "/",
"nodes": [
{
"createdIndex": 2,
"dir": true,
"key": "/foo_dir",
"modifiedIndex": 2,
"nodes": [
{
"createdIndex": 2,
"key": "/foo_dir/foo",
"modifiedIndex": 2,
"value": "bar"
}
]
}
]
}
}
```
@ -333,7 +452,15 @@ curl -L http://127.0.0.1:4001/v2/keys/foo_dir?recursive=true -XDELETE
```
```json
{"action":"delete","key":"/foo_dir","dir":true,"modifiedIndex":11}
{
"action": "delete",
"node": {
"createdIndex": 10,
"dir": true,
"key": "/foo_dir",
"modifiedIndex": 11
}
}
```
@ -349,7 +476,15 @@ curl -L http://127.0.0.1:4001/v2/keys/_message -XPUT -d value="Hello hidden worl
```
```json
{"action":"set","key":"/_message","value":"Hello hidden world","modifiedIndex":12}
{
"action": "set",
"node": {
"createdIndex": 3,
"key": "/_message",
"modifiedIndex": 3,
"value": "Hello hidden world"
}
}
```
@ -360,7 +495,15 @@ curl -L http://127.0.0.1:4001/v2/keys/message -XPUT -d value="Hello world"
```
```json
{"action":"set","key":"/message","value":"Hello world","modifiedIndex":13}
{
"action": "set",
"node": {
"createdIndex": 4,
"key": "/message",
"modifiedIndex": 4,
"value": "Hello world"
}
}
```
Now let's try to get a listing of keys under the root directory, `/`:
@ -370,7 +513,27 @@ curl -L http://127.0.0.1:4001/v2/keys/
```
```json
{"action":"get","key":"/","dir":true,"kvs":[{"key":"/foo","value":"two","modifiedIndex":9},{"key":"/message","value":"Hello world","modifiedIndex":13}],"modifiedIndex":0}
{
"action": "get",
"node": {
"dir": true,
"key": "/",
"nodes": [
{
"createdIndex": 2,
"dir": true,
"key": "/foo_dir",
"modifiedIndex": 2
},
{
"createdIndex": 4,
"key": "/message",
"modifiedIndex": 4,
"value": "Hello world"
}
]
}
}
```
Here we see the `/message` key but our hidden `/_message` key is not returned.
@ -421,7 +584,13 @@ SSLv3, TLS handshake, Finished (20):
And also the response from the etcd server:
```json
{"action":"set","key":"/foo","prevValue":"bar","value":"bar","modifiedIndex":3}
{
"action": "set",
"key": "/foo",
"modifiedIndex": 3,
"prevValue": "bar",
"value": "bar"
}
```
@ -468,7 +637,16 @@ TLS handshake, Finished (20)
And also the response from the server:
```json
{"action":"set","key":"/foo","prevValue":"bar","value":"bar","modifiedIndex":3}
{
"action": "set",
"node": {
"createdIndex": 12,
"key": "/foo",
"modifiedIndex": 12,
"prevValue": "two",
"value": "bar"
}
}
```
@ -516,7 +694,35 @@ curl -L http://127.0.0.1:4001/v2/keys/_etcd/machines
```
```json
[{"action":"get","key":"/_etcd/machines/machine1","value":"raft=http://127.0.0.1:7001\u0026etcd=http://127.0.0.1:4001","index":1},{"action":"get","key":"/_etcd/machines/machine2","value":"raft=http://127.0.0.1:7002\u0026etcd=http://127.0.0.1:4002","index":1},{"action":"get","key":"/_etcd/machines/machine3","value":"raft=http://127.0.0.1:7003\u0026etcd=http://127.0.0.1:4003","index":1}]
{
"action": "get",
"node": {
"createdIndex": 1,
"dir": true,
"key": "/_etcd/machines",
"modifiedIndex": 1,
"nodes": [
{
"createdIndex": 1,
"key": "/_etcd/machines/machine1",
"modifiedIndex": 1,
"value": "raft=http://127.0.0.1:7001&etcd=http://127.0.0.1:4001"
},
{
"createdIndex": 2,
"key": "/_etcd/machines/machine2",
"modifiedIndex": 2,
"value": "raft=http://127.0.0.1:7002&etcd=http://127.0.0.1:4002"
},
{
"createdIndex": 3,
"key": "/_etcd/machines/machine3",
"modifiedIndex": 3,
"value": "raft=http://127.0.0.1:7003&etcd=http://127.0.0.1:4003"
}
]
}
}
```
We can also get the current leader in the cluster:
@ -538,7 +744,15 @@ curl -L http://127.0.0.1:4001/v2/keys/foo -XPUT -d value=bar
```
```json
{"action":"set","key":"/foo","value":"bar","modifiedIndex":4}
{
"action": "set",
"node": {
"createdIndex": 4,
"key": "/foo",
"modifiedIndex": 4,
"value": "bar"
}
}
```
@ -579,7 +793,15 @@ curl -L http://127.0.0.1:4002/v2/keys/foo
```
```json
{"action":"get","key":"/foo","value":"bar","index":4}
{
"action": "get",
"node": {
"createdIndex": 4,
"key": "/foo",
"modifiedIndex": 4,
"value": "bar"
}
}
```

10
etcd.go
View File

@ -52,16 +52,6 @@ func main() {
profile(config.CPUProfileFile)
}
// Only guess the machine name if there is no data dir specified
// because the info file will should have our name
if config.Name == "" && config.DataDir == "" {
config.NameFromHostname()
}
if config.DataDir == "" && config.Name != "" {
config.DataDirFromName()
}
if config.DataDir == "" {
log.Fatal("The data dir was not set and could not be guessed from machine name")
}

View File

@ -0,0 +1,128 @@
package v2
import (
"net/http"
"path"
"strconv"
"time"
"github.com/coreos/go-etcd/etcd"
"github.com/gorilla/mux"
)
// acquireHandler attempts to acquire a lock on the given key.
// The "key" parameter specifies the resource to lock.
// The "ttl" parameter specifies how long the lock will persist for.
// The "timeout" parameter specifies how long the request should wait for the lock.
func (h *handler) acquireHandler(w http.ResponseWriter, req *http.Request) {
h.client.SyncCluster()
// Setup connection watcher.
closeNotifier, _ := w.(http.CloseNotifier)
closeChan := closeNotifier.CloseNotify()
// Parse "key" and "ttl" query parameters.
vars := mux.Vars(req)
keypath := path.Join(prefix, vars["key"])
ttl, err := strconv.Atoi(req.FormValue("ttl"))
if err != nil {
http.Error(w, "invalid ttl: " + err.Error(), http.StatusInternalServerError)
return
}
// Parse "timeout" parameter.
var timeout int
if len(req.FormValue("timeout")) == 0 {
timeout = -1
} else if timeout, err = strconv.Atoi(req.FormValue("timeout")); err != nil {
http.Error(w, "invalid timeout: " + err.Error(), http.StatusInternalServerError)
return
}
timeout = timeout + 1
// Create an incrementing id for the lock.
resp, err := h.client.AddChild(keypath, "-", uint64(ttl))
if err != nil {
http.Error(w, "add lock index error: " + err.Error(), http.StatusInternalServerError)
return
}
indexpath := resp.Node.Key
// Keep updating TTL to make sure lock request is not expired before acquisition.
stop := make(chan bool)
go h.ttlKeepAlive(indexpath, ttl, stop)
// Monitor for broken connection.
stopWatchChan := make(chan bool)
go func() {
select {
case <-closeChan:
stopWatchChan <- true
case <-stop:
// Stop watching for connection disconnect.
}
}()
// Extract the lock index.
index, _ := strconv.Atoi(path.Base(resp.Node.Key))
// Wait until we successfully get a lock or we get a failure.
var success bool
for {
// Read all indices.
resp, err = h.client.Get(keypath, true, true)
if err != nil {
http.Error(w, "lock children lookup error: " + err.Error(), http.StatusInternalServerError)
break
}
indices := extractResponseIndices(resp)
waitIndex := resp.Node.ModifiedIndex
prevIndex := findPrevIndex(indices, index)
// If there is no previous index then we have the lock.
if prevIndex == 0 {
success = true
break
}
// Otherwise watch previous index until it's gone.
_, err = h.client.Watch(path.Join(keypath, strconv.Itoa(prevIndex)), waitIndex, false, nil, stopWatchChan)
if err == etcd.ErrWatchStoppedByUser {
break
} else if err != nil {
http.Error(w, "lock watch error: " + err.Error(), http.StatusInternalServerError)
break
}
}
// Check for connection disconnect before we write the lock index.
select {
case <-stopWatchChan:
success = false
default:
}
// Stop the ttl keep-alive.
close(stop)
if success {
// Write lock index to response body if we acquire the lock.
h.client.Update(indexpath, "-", uint64(ttl))
w.Write([]byte(strconv.Itoa(index)))
} else {
// Make sure key is deleted if we couldn't acquire.
h.client.Delete(indexpath, false)
}
}
// ttlKeepAlive continues to update a key's TTL until the stop channel is closed.
func (h *handler) ttlKeepAlive(k string, ttl int, stop chan bool) {
for {
select {
case <-time.After(time.Duration(ttl / 2) * time.Second):
h.client.Update(k, "-", uint64(ttl))
case <-stop:
return
}
}
}

View File

@ -0,0 +1,30 @@
package v2
import (
"net/http"
"path"
"strconv"
"github.com/gorilla/mux"
)
// getIndexHandler retrieves the current lock index.
func (h *handler) getIndexHandler(w http.ResponseWriter, req *http.Request) {
h.client.SyncCluster()
vars := mux.Vars(req)
keypath := path.Join(prefix, vars["key"])
// Read all indices.
resp, err := h.client.Get(keypath, true, true)
if err != nil {
http.Error(w, "lock children lookup error: " + err.Error(), http.StatusInternalServerError)
return
}
// Write out the index of the last one to the response body.
indices := extractResponseIndices(resp)
if len(indices) > 0 {
w.Write([]byte(strconv.Itoa(indices[0])))
}
}

58
mod/lock/v2/handler.go Normal file
View File

@ -0,0 +1,58 @@
package v2
import (
"net/http"
"path"
"strconv"
"sort"
"github.com/gorilla/mux"
"github.com/coreos/go-etcd/etcd"
)
const prefix = "/_etcd/mod/lock"
// handler manages the lock HTTP request.
type handler struct {
*mux.Router
client *etcd.Client
}
// NewHandler creates an HTTP handler that can be registered on a router.
func NewHandler(addr string) (http.Handler) {
h := &handler{
Router: mux.NewRouter(),
client: etcd.NewClient([]string{addr}),
}
h.StrictSlash(false)
h.HandleFunc("/{key:.*}", h.getIndexHandler).Methods("GET")
h.HandleFunc("/{key:.*}", h.acquireHandler).Methods("POST")
h.HandleFunc("/{key_with_index:.*}", h.renewLockHandler).Methods("PUT")
h.HandleFunc("/{key_with_index:.*}", h.releaseLockHandler).Methods("DELETE")
return h
}
// extractResponseIndices extracts a sorted list of indicies from a response.
func extractResponseIndices(resp *etcd.Response) []int {
var indices []int
for _, node := range resp.Node.Nodes {
if index, _ := strconv.Atoi(path.Base(node.Key)); index > 0 {
indices = append(indices, index)
}
}
sort.Ints(indices)
return indices
}
// findPrevIndex retrieves the previous index before the given index.
func findPrevIndex(indices []int, idx int) int {
var prevIndex int
for _, index := range indices {
if index == idx {
break
}
prevIndex = index
}
return prevIndex
}

View File

@ -0,0 +1,24 @@
package v2
import (
"path"
"net/http"
"github.com/gorilla/mux"
)
// releaseLockHandler deletes the lock.
func (h *handler) releaseLockHandler(w http.ResponseWriter, req *http.Request) {
h.client.SyncCluster()
vars := mux.Vars(req)
keypath := path.Join(prefix, vars["key_with_index"])
// Delete the lock.
_, err := h.client.Delete(keypath, false)
if err != nil {
http.Error(w, "delete lock index error: " + err.Error(), http.StatusInternalServerError)
return
}
}

View File

@ -0,0 +1,30 @@
package v2
import (
"path"
"net/http"
"strconv"
"github.com/gorilla/mux"
)
// renewLockHandler attempts to update the TTL on an existing lock.
// Returns a 200 OK if successful. Returns non-200 on error.
func (h *handler) renewLockHandler(w http.ResponseWriter, req *http.Request) {
h.client.SyncCluster()
vars := mux.Vars(req)
keypath := path.Join(prefix, vars["key_with_index"])
ttl, err := strconv.Atoi(req.FormValue("ttl"))
if err != nil {
http.Error(w, "invalid ttl: " + err.Error(), http.StatusInternalServerError)
return
}
// Renew the lock, if it exists.
_, err = h.client.Update(keypath, "-", uint64(ttl))
if err != nil {
http.Error(w, "renew lock index error: " + err.Error(), http.StatusInternalServerError)
return
}
}

View File

@ -0,0 +1,188 @@
package lock
import (
"fmt"
"testing"
"time"
"github.com/coreos/etcd/server"
"github.com/coreos/etcd/tests"
"github.com/stretchr/testify/assert"
)
// Ensure that a lock can be acquired and released.
func TestModLockAcquireAndRelease(t *testing.T) {
tests.RunServer(func(s *server.Server) {
// Acquire lock.
body, err := testAcquireLock(s, "foo", 10)
assert.NoError(t, err)
assert.Equal(t, body, "2")
// Check that we have the lock.
body, err = testGetLockIndex(s, "foo")
assert.NoError(t, err)
assert.Equal(t, body, "2")
// Release lock.
body, err = testReleaseLock(s, "foo", 2)
assert.NoError(t, err)
assert.Equal(t, body, "")
// Check that we have the lock.
body, err = testGetLockIndex(s, "foo")
assert.NoError(t, err)
assert.Equal(t, body, "")
})
}
// Ensure that a lock can be acquired and another process is blocked until released.
func TestModLockBlockUntilAcquire(t *testing.T) {
tests.RunServer(func(s *server.Server) {
c := make(chan bool)
// Acquire lock #1.
go func() {
body, err := testAcquireLock(s, "foo", 10)
assert.NoError(t, err)
assert.Equal(t, body, "2")
c <- true
}()
<- c
// Acquire lock #2.
go func() {
c <- true
body, err := testAcquireLock(s, "foo", 10)
assert.NoError(t, err)
assert.Equal(t, body, "4")
}()
<- c
time.Sleep(1 * time.Second)
// Check that we have the lock #1.
body, err := testGetLockIndex(s, "foo")
assert.NoError(t, err)
assert.Equal(t, body, "2")
// Release lock #1.
body, err = testReleaseLock(s, "foo", 2)
assert.NoError(t, err)
// Check that we have lock #2.
body, err = testGetLockIndex(s, "foo")
assert.NoError(t, err)
assert.Equal(t, body, "4")
// Release lock #2.
body, err = testReleaseLock(s, "foo", 4)
assert.NoError(t, err)
// Check that we have no lock.
body, err = testGetLockIndex(s, "foo")
assert.NoError(t, err)
assert.Equal(t, body, "")
})
}
// Ensure that a lock will be released after the TTL.
func TestModLockExpireAndRelease(t *testing.T) {
tests.RunServer(func(s *server.Server) {
c := make(chan bool)
// Acquire lock #1.
go func() {
body, err := testAcquireLock(s, "foo", 2)
assert.NoError(t, err)
assert.Equal(t, body, "2")
c <- true
}()
<- c
// Acquire lock #2.
go func() {
c <- true
body, err := testAcquireLock(s, "foo", 10)
assert.NoError(t, err)
assert.Equal(t, body, "4")
}()
<- c
time.Sleep(1 * time.Second)
// Check that we have the lock #1.
body, err := testGetLockIndex(s, "foo")
assert.NoError(t, err)
assert.Equal(t, body, "2")
// Wait for lock #1 TTL.
time.Sleep(2 * time.Second)
// Check that we have lock #2.
body, err = testGetLockIndex(s, "foo")
assert.NoError(t, err)
assert.Equal(t, body, "4")
})
}
// Ensure that a lock can be renewed.
func TestModLockRenew(t *testing.T) {
tests.RunServer(func(s *server.Server) {
// Acquire lock.
body, err := testAcquireLock(s, "foo", 3)
assert.NoError(t, err)
assert.Equal(t, body, "2")
time.Sleep(2 * time.Second)
// Check that we have the lock.
body, err = testGetLockIndex(s, "foo")
assert.NoError(t, err)
assert.Equal(t, body, "2")
// Renew lock.
body, err = testRenewLock(s, "foo", 2, 3)
assert.NoError(t, err)
assert.Equal(t, body, "")
time.Sleep(2 * time.Second)
// Check that we still have the lock.
body, err = testGetLockIndex(s, "foo")
assert.NoError(t, err)
assert.Equal(t, body, "2")
time.Sleep(2 * time.Second)
// Check that lock was released.
body, err = testGetLockIndex(s, "foo")
assert.NoError(t, err)
assert.Equal(t, body, "")
})
}
func testAcquireLock(s *server.Server, key string, ttl int) (string, error) {
resp, err := tests.PostForm(fmt.Sprintf("%s/mod/v2/lock/%s?ttl=%d", s.URL(), key, ttl), nil)
ret := tests.ReadBody(resp)
return string(ret), err
}
func testGetLockIndex(s *server.Server, key string) (string, error) {
resp, err := tests.Get(fmt.Sprintf("%s/mod/v2/lock/%s", s.URL(), key))
ret := tests.ReadBody(resp)
return string(ret), err
}
func testReleaseLock(s *server.Server, key string, index int) (string, error) {
resp, err := tests.DeleteForm(fmt.Sprintf("%s/mod/v2/lock/%s/%d", s.URL(), key, index), nil)
ret := tests.ReadBody(resp)
return string(ret), err
}
func testRenewLock(s *server.Server, key string, index int, ttl int) (string, error) {
resp, err := tests.PutForm(fmt.Sprintf("%s/mod/v2/lock/%s/%d?ttl=%d", s.URL(), key, index, ttl), nil)
ret := tests.ReadBody(resp)
return string(ret), err
}

View File

@ -6,6 +6,7 @@ import (
"path"
"github.com/coreos/etcd/mod/dashboard"
lock2 "github.com/coreos/etcd/mod/lock/v2"
"github.com/gorilla/mux"
)
@ -16,11 +17,12 @@ func addSlash(w http.ResponseWriter, req *http.Request) {
return
}
func HttpHandler() (handler http.Handler) {
modMux := mux.NewRouter()
modMux.HandleFunc("/dashboard", addSlash)
modMux.PathPrefix("/dashboard/").
Handler(http.StripPrefix("/dashboard/", dashboard.HttpHandler()))
func HttpHandler(addr string) http.Handler {
r := mux.NewRouter()
r.HandleFunc("/dashboard", addSlash)
r.PathPrefix("/dashboard/").Handler(http.StripPrefix("/dashboard/", dashboard.HttpHandler()))
return modMux
// TODO: Use correct addr.
r.PathPrefix("/v2/lock").Handler(http.StripPrefix("/v2/lock", lock2.NewHandler(addr)))
return r
}

View File

@ -134,6 +134,11 @@ func (c *Config) Load(arguments []string) error {
return fmt.Errorf("sanitize: %v", err)
}
// Force remove server configuration if specified.
if c.Force {
c.Reset()
}
return nil
}
@ -284,11 +289,6 @@ func (c *Config) LoadFlags(arguments []string) error {
c.CorsOrigins = trimsplit(cors, ",")
}
// Force remove server configuration if specified.
if c.Force {
c.Reset()
}
return nil
}
@ -410,6 +410,16 @@ func (c *Config) Sanitize() error {
return fmt.Errorf("Peer Listen Host: %s", err)
}
// Only guess the machine name if there is no data dir specified
// because the info file should have our name
if c.Name == "" && c.DataDir == "" {
c.NameFromHostname()
}
if c.DataDir == "" && c.Name != "" {
c.DataDirFromName()
}
return nil
}
@ -441,7 +451,7 @@ func (c *Config) PeerTLSConfig() (TLSConfig, error) {
return c.PeerTLSInfo().Config()
}
// sanitizeURL will cleanup a host string in the format hostname:port and
// sanitizeURL will cleanup a host string in the format hostname[:port] and
// attach a schema.
func sanitizeURL(host string, defaultScheme string) (string, error) {
// Blank URLs are fine input, just return it
@ -472,15 +482,23 @@ func sanitizeBindAddr(bindAddr string, addr string) (string, error) {
return "", err
}
ahost, aport, err := net.SplitHostPort(aurl.Host)
// If it is a valid host:port simply return with no further checks.
bhost, bport, err := net.SplitHostPort(bindAddr)
if err == nil && bhost != "" {
return bindAddr, nil
}
// SplitHostPort makes the host optional, but we don't want that.
if bhost == "" && bport != "" {
return "", fmt.Errorf("IP required can't use a port only")
}
// bindAddr doesn't have a port if we reach here so take the port from the
// advertised URL.
_, aport, err := net.SplitHostPort(aurl.Host)
if err != nil {
return "", err
}
// If the listen host isn't set use the advertised host
if bindAddr == "" {
bindAddr = ahost
}
return net.JoinHostPort(bindAddr, aport), nil
}

View File

@ -223,6 +223,29 @@ func TestConfigBindAddrFlag(t *testing.T) {
assert.Equal(t, c.BindAddr, "127.0.0.1:4003", "")
}
// Ensures that a the Listen Host port overrides the advertised port
func TestConfigBindAddrOverride(t *testing.T) {
c := NewConfig()
assert.Nil(t, c.LoadFlags([]string{"-addr", "127.0.0.1:4009", "-bind-addr", "127.0.0.1:4010"}), "")
assert.Nil(t, c.Sanitize())
assert.Equal(t, c.BindAddr, "127.0.0.1:4010", "")
}
// Ensures that a the Listen Host inherits its port from the advertised addr
func TestConfigBindAddrInheritPort(t *testing.T) {
c := NewConfig()
assert.Nil(t, c.LoadFlags([]string{"-addr", "127.0.0.1:4009", "-bind-addr", "127.0.0.1"}), "")
assert.Nil(t, c.Sanitize())
assert.Equal(t, c.BindAddr, "127.0.0.1:4009", "")
}
// Ensures that a port only argument errors out
func TestConfigBindAddrErrorOnNoHost(t *testing.T) {
c := NewConfig()
assert.Nil(t, c.LoadFlags([]string{"-addr", "127.0.0.1:4009", "-bind-addr", ":4010"}), "")
assert.Error(t, c.Sanitize())
}
// Ensures that the peers can be parsed from the environment.
func TestConfigPeersEnv(t *testing.T) {
withEnv("ETCD_PEERS", "coreos.com:4001,coreos.com:4002", func(c *Config) {
@ -313,6 +336,24 @@ func TestConfigNameFlag(t *testing.T) {
assert.Equal(t, c.Name, "test-name", "")
}
// Ensures that a Name gets guessed if not specified
func TestConfigNameGuess(t *testing.T) {
c := NewConfig()
assert.Nil(t, c.LoadFlags([]string{}), "")
assert.Nil(t, c.Sanitize())
name, _ := os.Hostname()
assert.Equal(t, c.Name, name, "")
}
// Ensures that a DataDir gets guessed if not specified
func TestConfigDataDirGuess(t *testing.T) {
c := NewConfig()
assert.Nil(t, c.LoadFlags([]string{}), "")
assert.Nil(t, c.Sanitize())
name, _ := os.Hostname()
assert.Equal(t, c.DataDir, name+".etcd", "")
}
// Ensures that Snapshot can be parsed from the environment.
func TestConfigSnapshotEnv(t *testing.T) {
withEnv("ETCD_SNAPSHOT", "1", func(c *Config) {

View File

@ -135,7 +135,7 @@ func (s *Server) installV2() {
func (s *Server) installMod() {
r := s.router
r.PathPrefix("/mod").Handler(http.StripPrefix("/mod", mod.HttpHandler()))
r.PathPrefix("/mod").Handler(http.StripPrefix("/mod", mod.HttpHandler(s.url)))
}
// Adds a v1 server handler to the router.

View File

@ -31,15 +31,15 @@ Cluster Configuration Options:
should match the peer's '-peer-addr' flag.
Client Communication Options:
-addr=<host:port> The public host:port used for client communication.
-bind-addr=<host> The listening hostname used for client communication.
-ca-file=<path> Path to the client CA file.
-cert-file=<path> Path to the client cert file.
-key-file=<path> Path to the client key file.
-addr=<host:port> The public host:port used for client communication.
-bind-addr=<host[:port]> The listening host:port used for client communication.
-ca-file=<path> Path to the client CA file.
-cert-file=<path> Path to the client cert file.
-key-file=<path> Path to the client key file.
Peer Communication Options:
-peer-addr=<host:port> The public host:port used for peer communication.
-peer-bind-addr=<host> The listening hostname used for peer communication.
-peer-bind-addr=<host[:port]> The listening host:port used for peer communication.
-peer-ca-file=<path> Path to the peer CA file.
-peer-cert-file=<path> Path to the peer cert file.
-peer-key-file=<path> Path to the peer key file.

View File

@ -4,6 +4,7 @@ import (
"encoding/json"
"fmt"
"net/http"
"net/url"
"strconv"
etcdErr "github.com/coreos/etcd/error"
@ -24,9 +25,17 @@ func GetHandler(w http.ResponseWriter, req *http.Request, s Server) error {
if req.FormValue("consistent") == "true" && s.State() != raft.Leader {
leader := s.Leader()
hostname, _ := s.ClientURL(leader)
url := hostname + req.URL.Path
log.Debugf("Redirect consistent get to %s", url)
http.Redirect(w, req, url, http.StatusTemporaryRedirect)
url, err := url.Parse(hostname)
if err != nil {
log.Warn("Redirect cannot parse hostName ", hostname)
return err
}
url.RawQuery = req.URL.RawQuery
url.Path = req.URL.Path
log.Debugf("Redirect consistent get to %s", url.String())
http.Redirect(w, req, url.String(), http.StatusTemporaryRedirect)
return nil
}

View File

@ -19,11 +19,11 @@ func TestV2DeleteKey(t *testing.T) {
tests.RunServer(func(s *server.Server) {
v := url.Values{}
v.Set("value", "XXX")
resp, err := tests.PutForm(fmt.Sprintf("http://%s%s", s.URL(), "/v2/keys/foo/bar"), v)
resp, err := tests.PutForm(fmt.Sprintf("%s%s", s.URL(), "/v2/keys/foo/bar"), v)
tests.ReadBody(resp)
resp, err = tests.DeleteForm(fmt.Sprintf("http://%s%s", s.URL(), "/v2/keys/foo/bar"), url.Values{})
resp, err = tests.DeleteForm(fmt.Sprintf("%s%s", s.URL(), "/v2/keys/foo/bar"), url.Values{})
body := tests.ReadBody(resp)
assert.Nil(t, err, "")
assert.Equal(t, string(body), `{"action":"delete","node":{"key":"/foo/bar","prevValue":"XXX","modifiedIndex":2,"createdIndex":1}}`, "")
assert.Equal(t, string(body), `{"action":"delete","node":{"key":"/foo/bar","prevValue":"XXX","modifiedIndex":3,"createdIndex":2}}`, "")
})
}

View File

@ -20,16 +20,15 @@ func TestV2GetKey(t *testing.T) {
tests.RunServer(func(s *server.Server) {
v := url.Values{}
v.Set("value", "XXX")
resp, _ := tests.PutForm(fmt.Sprintf("http://%s%s", s.URL(), "/v2/keys/foo/bar"), v)
resp, _ := tests.PutForm(fmt.Sprintf("%s%s", s.URL(), "/v2/keys/foo/bar"), v)
tests.ReadBody(resp)
resp, _ = tests.Get(fmt.Sprintf("http://%s%s", s.URL(), "/v2/keys/foo/bar"))
resp, _ = tests.Get(fmt.Sprintf("%s%s", s.URL(), "/v2/keys/foo/bar"))
body := tests.ReadBodyJSON(resp)
assert.Equal(t, body["action"], "get", "")
node := body["node"].(map[string]interface{})
assert.Equal(t, node["key"], "/foo/bar", "")
assert.Equal(t, node["value"], "XXX", "")
assert.Equal(t, node["modifiedIndex"], 1, "")
assert.Equal(t, node["modifiedIndex"], 2, "")
})
}
@ -44,21 +43,20 @@ func TestV2GetKeyRecursively(t *testing.T) {
v := url.Values{}
v.Set("value", "XXX")
v.Set("ttl", "10")
resp, _ := tests.PutForm(fmt.Sprintf("http://%s%s", s.URL(), "/v2/keys/foo/x"), v)
resp, _ := tests.PutForm(fmt.Sprintf("%s%s", s.URL(), "/v2/keys/foo/x"), v)
tests.ReadBody(resp)
v.Set("value", "YYY")
resp, _ = tests.PutForm(fmt.Sprintf("http://%s%s", s.URL(), "/v2/keys/foo/y/z"), v)
resp, _ = tests.PutForm(fmt.Sprintf("%s%s", s.URL(), "/v2/keys/foo/y/z"), v)
tests.ReadBody(resp)
resp, _ = tests.Get(fmt.Sprintf("http://%s%s", s.URL(), "/v2/keys/foo?recursive=true"))
resp, _ = tests.Get(fmt.Sprintf("%s%s", s.URL(), "/v2/keys/foo?recursive=true"))
body := tests.ReadBodyJSON(resp)
assert.Equal(t, body["action"], "get", "")
node := body["node"].(map[string]interface{})
assert.Equal(t, node["key"], "/foo", "")
assert.Equal(t, node["dir"], true, "")
assert.Equal(t, node["modifiedIndex"], 1, "")
assert.Equal(t, node["modifiedIndex"], 2, "")
assert.Equal(t, len(node["nodes"].([]interface{})), 2, "")
node0 := node["nodes"].([]interface{})[0].(map[string]interface{})
@ -86,7 +84,7 @@ func TestV2WatchKey(t *testing.T) {
var body map[string]interface{}
c := make(chan bool)
go func() {
resp, _ := tests.Get(fmt.Sprintf("http://%s%s", s.URL(), "/v2/keys/foo/bar?wait=true"))
resp, _ := tests.Get(fmt.Sprintf("%s%s", s.URL(), "/v2/keys/foo/bar?wait=true"))
body = tests.ReadBodyJSON(resp)
c <- true
}()
@ -98,7 +96,7 @@ func TestV2WatchKey(t *testing.T) {
// Set a value.
v := url.Values{}
v.Set("value", "XXX")
resp, _ := tests.PutForm(fmt.Sprintf("http://%s%s", s.URL(), "/v2/keys/foo/bar"), v)
resp, _ := tests.PutForm(fmt.Sprintf("%s%s", s.URL(), "/v2/keys/foo/bar"), v)
tests.ReadBody(resp)
// A response should follow from the GET above.
@ -117,7 +115,7 @@ func TestV2WatchKey(t *testing.T) {
node := body["node"].(map[string]interface{})
assert.Equal(t, node["key"], "/foo/bar", "")
assert.Equal(t, node["value"], "XXX", "")
assert.Equal(t, node["modifiedIndex"], 1, "")
assert.Equal(t, node["modifiedIndex"], 2, "")
})
}
@ -132,7 +130,7 @@ func TestV2WatchKeyWithIndex(t *testing.T) {
var body map[string]interface{}
c := make(chan bool)
go func() {
resp, _ := tests.Get(fmt.Sprintf("http://%s%s", s.URL(), "/v2/keys/foo/bar?wait=true&waitIndex=2"))
resp, _ := tests.Get(fmt.Sprintf("%s%s", s.URL(), "/v2/keys/foo/bar?wait=true&waitIndex=3"))
body = tests.ReadBodyJSON(resp)
c <- true
}()
@ -144,7 +142,7 @@ func TestV2WatchKeyWithIndex(t *testing.T) {
// Set a value (before given index).
v := url.Values{}
v.Set("value", "XXX")
resp, _ := tests.PutForm(fmt.Sprintf("http://%s%s", s.URL(), "/v2/keys/foo/bar"), v)
resp, _ := tests.PutForm(fmt.Sprintf("%s%s", s.URL(), "/v2/keys/foo/bar"), v)
tests.ReadBody(resp)
// Make sure response didn't fire early.
@ -153,7 +151,7 @@ func TestV2WatchKeyWithIndex(t *testing.T) {
// Set a value (before given index).
v.Set("value", "YYY")
resp, _ = tests.PutForm(fmt.Sprintf("http://%s%s", s.URL(), "/v2/keys/foo/bar"), v)
resp, _ = tests.PutForm(fmt.Sprintf("%s%s", s.URL(), "/v2/keys/foo/bar"), v)
tests.ReadBody(resp)
// A response should follow from the GET above.
@ -172,6 +170,6 @@ func TestV2WatchKeyWithIndex(t *testing.T) {
node := body["node"].(map[string]interface{})
assert.Equal(t, node["key"], "/foo/bar", "")
assert.Equal(t, node["value"], "YYY", "")
assert.Equal(t, node["modifiedIndex"], 2, "")
assert.Equal(t, node["modifiedIndex"], 3, "")
})
}

View File

@ -18,25 +18,27 @@ import (
func TestV2CreateUnique(t *testing.T) {
tests.RunServer(func(s *server.Server) {
// POST should add index to list.
resp, _ := tests.PostForm(fmt.Sprintf("http://%s%s", s.URL(), "/v2/keys/foo/bar"), nil)
resp, _ := tests.PostForm(fmt.Sprintf("%s%s", s.URL(), "/v2/keys/foo/bar"), nil)
body := tests.ReadBodyJSON(resp)
assert.Equal(t, body["action"], "create", "")
node := body["node"].(map[string]interface{})
assert.Equal(t, node["key"], "/foo/bar/1", "")
assert.Equal(t, node["key"], "/foo/bar/2", "")
assert.Equal(t, node["dir"], true, "")
assert.Equal(t, node["modifiedIndex"], 1, "")
assert.Equal(t, node["modifiedIndex"], 2, "")
// Second POST should add next index to list.
resp, _ = tests.PostForm(fmt.Sprintf("http://%s%s", s.URL(), "/v2/keys/foo/bar"), nil)
resp, _ = tests.PostForm(fmt.Sprintf("%s%s", s.URL(), "/v2/keys/foo/bar"), nil)
body = tests.ReadBodyJSON(resp)
node = body["node"].(map[string]interface{})
assert.Equal(t, node["key"], "/foo/bar/2", "")
assert.Equal(t, node["key"], "/foo/bar/3", "")
// POST to a different key should add index to that list.
resp, _ = tests.PostForm(fmt.Sprintf("http://%s%s", s.URL(), "/v2/keys/foo/baz"), nil)
resp, _ = tests.PostForm(fmt.Sprintf("%s%s", s.URL(), "/v2/keys/foo/baz"), nil)
body = tests.ReadBodyJSON(resp)
node = body["node"].(map[string]interface{})
assert.Equal(t, node["key"], "/foo/baz/3", "")
assert.Equal(t, node["key"], "/foo/baz/4", "")
})
}

View File

@ -19,10 +19,10 @@ func TestV2SetKey(t *testing.T) {
tests.RunServer(func(s *server.Server) {
v := url.Values{}
v.Set("value", "XXX")
resp, err := tests.PutForm(fmt.Sprintf("http://%s%s", s.URL(), "/v2/keys/foo/bar"), v)
resp, err := tests.PutForm(fmt.Sprintf("%s%s", s.URL(), "/v2/keys/foo/bar"), v)
body := tests.ReadBody(resp)
assert.Nil(t, err, "")
assert.Equal(t, string(body), `{"action":"set","node":{"key":"/foo/bar","value":"XXX","modifiedIndex":1,"createdIndex":1}}`, "")
assert.Equal(t, string(body), `{"action":"set","node":{"key":"/foo/bar","value":"XXX","modifiedIndex":2,"createdIndex":2}}`, "")
})
}
@ -36,7 +36,7 @@ func TestV2SetKeyWithTTL(t *testing.T) {
v := url.Values{}
v.Set("value", "XXX")
v.Set("ttl", "20")
resp, _ := tests.PutForm(fmt.Sprintf("http://%s%s", s.URL(), "/v2/keys/foo/bar"), v)
resp, _ := tests.PutForm(fmt.Sprintf("%s%s", s.URL(), "/v2/keys/foo/bar"), v)
body := tests.ReadBodyJSON(resp)
node := body["node"].(map[string]interface{})
assert.Equal(t, node["ttl"], 20, "")
@ -56,7 +56,7 @@ func TestV2SetKeyWithBadTTL(t *testing.T) {
v := url.Values{}
v.Set("value", "XXX")
v.Set("ttl", "bad_ttl")
resp, _ := tests.PutForm(fmt.Sprintf("http://%s%s", s.URL(), "/v2/keys/foo/bar"), v)
resp, _ := tests.PutForm(fmt.Sprintf("%s%s", s.URL(), "/v2/keys/foo/bar"), v)
body := tests.ReadBodyJSON(resp)
assert.Equal(t, body["errorCode"], 202, "")
assert.Equal(t, body["message"], "The given TTL in POST form is not a number", "")
@ -73,7 +73,7 @@ func TestV2CreateKeySuccess(t *testing.T) {
v := url.Values{}
v.Set("value", "XXX")
v.Set("prevExist", "false")
resp, _ := tests.PutForm(fmt.Sprintf("http://%s%s", s.URL(), "/v2/keys/foo/bar"), v)
resp, _ := tests.PutForm(fmt.Sprintf("%s%s", s.URL(), "/v2/keys/foo/bar"), v)
body := tests.ReadBodyJSON(resp)
node := body["node"].(map[string]interface{})
assert.Equal(t, node["value"], "XXX", "")
@ -90,9 +90,9 @@ func TestV2CreateKeyFail(t *testing.T) {
v := url.Values{}
v.Set("value", "XXX")
v.Set("prevExist", "false")
resp, _ := tests.PutForm(fmt.Sprintf("http://%s%s", s.URL(), "/v2/keys/foo/bar"), v)
resp, _ := tests.PutForm(fmt.Sprintf("%s%s", s.URL(), "/v2/keys/foo/bar"), v)
tests.ReadBody(resp)
resp, _ = tests.PutForm(fmt.Sprintf("http://%s%s", s.URL(), "/v2/keys/foo/bar"), v)
resp, _ = tests.PutForm(fmt.Sprintf("%s%s", s.URL(), "/v2/keys/foo/bar"), v)
body := tests.ReadBodyJSON(resp)
assert.Equal(t, body["errorCode"], 105, "")
assert.Equal(t, body["message"], "Already exists", "")
@ -110,12 +110,12 @@ func TestV2UpdateKeySuccess(t *testing.T) {
v := url.Values{}
v.Set("value", "XXX")
resp, _ := tests.PutForm(fmt.Sprintf("http://%s%s", s.URL(), "/v2/keys/foo/bar"), v)
resp, _ := tests.PutForm(fmt.Sprintf("%s%s", s.URL(), "/v2/keys/foo/bar"), v)
tests.ReadBody(resp)
v.Set("value", "YYY")
v.Set("prevExist", "true")
resp, _ = tests.PutForm(fmt.Sprintf("http://%s%s", s.URL(), "/v2/keys/foo/bar"), v)
resp, _ = tests.PutForm(fmt.Sprintf("%s%s", s.URL(), "/v2/keys/foo/bar"), v)
body := tests.ReadBodyJSON(resp)
assert.Equal(t, body["action"], "update", "")
@ -131,11 +131,11 @@ func TestV2UpdateKeySuccess(t *testing.T) {
func TestV2UpdateKeyFailOnValue(t *testing.T) {
tests.RunServer(func(s *server.Server) {
v := url.Values{}
resp, _ := tests.PutForm(fmt.Sprintf("http://%s%s", s.URL(), "/v2/keys/foo"), v)
resp, _ := tests.PutForm(fmt.Sprintf("%s%s", s.URL(), "/v2/keys/foo"), v)
v.Set("value", "YYY")
v.Set("prevExist", "true")
resp, _ = tests.PutForm(fmt.Sprintf("http://%s%s", s.URL(), "/v2/keys/foo/bar"), v)
resp, _ = tests.PutForm(fmt.Sprintf("%s%s", s.URL(), "/v2/keys/foo/bar"), v)
body := tests.ReadBodyJSON(resp)
assert.Equal(t, body["errorCode"], 100, "")
assert.Equal(t, body["message"], "Key Not Found", "")
@ -153,7 +153,7 @@ func TestV2UpdateKeyFailOnMissingDirectory(t *testing.T) {
v := url.Values{}
v.Set("value", "YYY")
v.Set("prevExist", "true")
resp, _ := tests.PutForm(fmt.Sprintf("http://%s%s", s.URL(), "/v2/keys/foo/bar"), v)
resp, _ := tests.PutForm(fmt.Sprintf("%s%s", s.URL(), "/v2/keys/foo/bar"), v)
body := tests.ReadBodyJSON(resp)
assert.Equal(t, body["errorCode"], 100, "")
assert.Equal(t, body["message"], "Key Not Found", "")
@ -170,18 +170,17 @@ func TestV2SetKeyCASOnIndexSuccess(t *testing.T) {
tests.RunServer(func(s *server.Server) {
v := url.Values{}
v.Set("value", "XXX")
resp, _ := tests.PutForm(fmt.Sprintf("http://%s%s", s.URL(), "/v2/keys/foo/bar"), v)
resp, _ := tests.PutForm(fmt.Sprintf("%s%s", s.URL(), "/v2/keys/foo/bar"), v)
tests.ReadBody(resp)
v.Set("value", "YYY")
v.Set("prevIndex", "1")
resp, _ = tests.PutForm(fmt.Sprintf("http://%s%s", s.URL(), "/v2/keys/foo/bar"), v)
v.Set("prevIndex", "2")
resp, _ = tests.PutForm(fmt.Sprintf("%s%s", s.URL(), "/v2/keys/foo/bar"), v)
body := tests.ReadBodyJSON(resp)
assert.Equal(t, body["action"], "compareAndSwap", "")
node := body["node"].(map[string]interface{})
assert.Equal(t, node["prevValue"], "XXX", "")
assert.Equal(t, node["value"], "YYY", "")
assert.Equal(t, node["modifiedIndex"], 2, "")
assert.Equal(t, node["modifiedIndex"], 3, "")
})
}
@ -194,16 +193,16 @@ func TestV2SetKeyCASOnIndexFail(t *testing.T) {
tests.RunServer(func(s *server.Server) {
v := url.Values{}
v.Set("value", "XXX")
resp, _ := tests.PutForm(fmt.Sprintf("http://%s%s", s.URL(), "/v2/keys/foo/bar"), v)
resp, _ := tests.PutForm(fmt.Sprintf("%s%s", s.URL(), "/v2/keys/foo/bar"), v)
tests.ReadBody(resp)
v.Set("value", "YYY")
v.Set("prevIndex", "10")
resp, _ = tests.PutForm(fmt.Sprintf("http://%s%s", s.URL(), "/v2/keys/foo/bar"), v)
resp, _ = tests.PutForm(fmt.Sprintf("%s%s", s.URL(), "/v2/keys/foo/bar"), v)
body := tests.ReadBodyJSON(resp)
assert.Equal(t, body["errorCode"], 101, "")
assert.Equal(t, body["message"], "Test Failed", "")
assert.Equal(t, body["cause"], "[ != XXX] [10 != 1]", "")
assert.Equal(t, body["index"], 1, "")
assert.Equal(t, body["cause"], "[ != XXX] [10 != 2]", "")
assert.Equal(t, body["index"], 2, "")
})
}
@ -216,7 +215,7 @@ func TestV2SetKeyCASWithInvalidIndex(t *testing.T) {
v := url.Values{}
v.Set("value", "YYY")
v.Set("prevIndex", "bad_index")
resp, _ := tests.PutForm(fmt.Sprintf("http://%s%s", s.URL(), "/v2/keys/foo/bar"), v)
resp, _ := tests.PutForm(fmt.Sprintf("%s%s", s.URL(), "/v2/keys/foo/bar"), v)
body := tests.ReadBodyJSON(resp)
assert.Equal(t, body["errorCode"], 203, "")
assert.Equal(t, body["message"], "The given index in POST form is not a number", "")
@ -233,18 +232,17 @@ func TestV2SetKeyCASOnValueSuccess(t *testing.T) {
tests.RunServer(func(s *server.Server) {
v := url.Values{}
v.Set("value", "XXX")
resp, _ := tests.PutForm(fmt.Sprintf("http://%s%s", s.URL(), "/v2/keys/foo/bar"), v)
resp, _ := tests.PutForm(fmt.Sprintf("%s%s", s.URL(), "/v2/keys/foo/bar"), v)
tests.ReadBody(resp)
v.Set("value", "YYY")
v.Set("prevValue", "XXX")
resp, _ = tests.PutForm(fmt.Sprintf("http://%s%s", s.URL(), "/v2/keys/foo/bar"), v)
resp, _ = tests.PutForm(fmt.Sprintf("%s%s", s.URL(), "/v2/keys/foo/bar"), v)
body := tests.ReadBodyJSON(resp)
assert.Equal(t, body["action"], "compareAndSwap", "")
node := body["node"].(map[string]interface{})
assert.Equal(t, node["prevValue"], "XXX", "")
assert.Equal(t, node["value"], "YYY", "")
assert.Equal(t, node["modifiedIndex"], 2, "")
assert.Equal(t, node["modifiedIndex"], 3, "")
})
}
@ -257,16 +255,16 @@ func TestV2SetKeyCASOnValueFail(t *testing.T) {
tests.RunServer(func(s *server.Server) {
v := url.Values{}
v.Set("value", "XXX")
resp, _ := tests.PutForm(fmt.Sprintf("http://%s%s", s.URL(), "/v2/keys/foo/bar"), v)
resp, _ := tests.PutForm(fmt.Sprintf("%s%s", s.URL(), "/v2/keys/foo/bar"), v)
tests.ReadBody(resp)
v.Set("value", "YYY")
v.Set("prevValue", "AAA")
resp, _ = tests.PutForm(fmt.Sprintf("http://%s%s", s.URL(), "/v2/keys/foo/bar"), v)
resp, _ = tests.PutForm(fmt.Sprintf("%s%s", s.URL(), "/v2/keys/foo/bar"), v)
body := tests.ReadBodyJSON(resp)
assert.Equal(t, body["errorCode"], 101, "")
assert.Equal(t, body["message"], "Test Failed", "")
assert.Equal(t, body["cause"], "[AAA != XXX] [0 != 1]", "")
assert.Equal(t, body["index"], 1, "")
assert.Equal(t, body["cause"], "[AAA != XXX] [0 != 2]", "")
assert.Equal(t, body["index"], 2, "")
})
}
@ -279,7 +277,7 @@ func TestV2SetKeyCASWithMissingValueFails(t *testing.T) {
v := url.Values{}
v.Set("value", "XXX")
v.Set("prevValue", "")
resp, _ := tests.PutForm(fmt.Sprintf("http://%s%s", s.URL(), "/v2/keys/foo/bar"), v)
resp, _ := tests.PutForm(fmt.Sprintf("%s%s", s.URL(), "/v2/keys/foo/bar"), v)
body := tests.ReadBodyJSON(resp)
assert.Equal(t, body["errorCode"], 201, "")
assert.Equal(t, body["message"], "PrevValue is Required in POST form", "")

17
test.sh
View File

@ -1,6 +1,10 @@
#!/bin/sh
set -e
if [ -z "$PKG" ]; then
PKG="./store ./server ./server/v2/tests ./mod/lock/v2/tests"
fi
# Get GOPATH, etc from build
. ./build
@ -8,14 +12,11 @@ set -e
export GOPATH="${PWD}"
# Unit tests
go test -i ./server
go test -v ./server
go test -i ./server/v2/tests
go test -v ./server/v2/tests
go test -i ./store
go test -v ./store
for i in $PKG
do
go test -i $i
go test -v $i
done
# Functional tests
go test -i ./tests/functional

View File

@ -25,8 +25,10 @@ func RunServer(f func(*server.Server)) {
store := store.New()
registry := server.NewRegistry(store)
ps := server.NewPeerServer(testName, path, testRaftURL, testRaftURL, &server.TLSConfig{Scheme: "http"}, &server.TLSInfo{}, registry, store, testSnapshotCount, testHeartbeatTimeout, testElectionTimeout)
s := server.New(testName, testClientURL, testClientURL, &server.TLSConfig{Scheme: "http"}, &server.TLSInfo{}, ps, registry, store)
ps := server.NewPeerServer(testName, path, "http://" + testRaftURL, testRaftURL, &server.TLSConfig{Scheme: "http"}, &server.TLSInfo{}, registry, store, testSnapshotCount, testHeartbeatTimeout, testElectionTimeout)
ps.MaxClusterSize = 9
s := server.New(testName, "http://" + testClientURL, testClientURL, &server.TLSConfig{Scheme: "http"}, &server.TLSInfo{}, ps, registry, store)
ps.SetServer(s)
// Start up peer server.