diff --git a/Dockerfile b/Dockerfile index 8b549a465..4b3a9bf06 100644 --- a/Dockerfile +++ b/Dockerfile @@ -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"] \ No newline at end of file +ENTRYPOINT ["/opt/etcd/etcd"] + diff --git a/README.md b/README.md index a74363eb4..a95da5eec 100644 --- a/README.md +++ b/README.md @@ -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" + } +} ``` diff --git a/etcd.go b/etcd.go index 090bda6a1..5c3d7d3cb 100644 --- a/etcd.go +++ b/etcd.go @@ -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") } diff --git a/mod/lock/v2/acquire_handler.go b/mod/lock/v2/acquire_handler.go new file mode 100644 index 000000000..d6fa2aacb --- /dev/null +++ b/mod/lock/v2/acquire_handler.go @@ -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 + } + } +} diff --git a/mod/lock/v2/get_index_handler.go b/mod/lock/v2/get_index_handler.go new file mode 100644 index 000000000..73ea663ff --- /dev/null +++ b/mod/lock/v2/get_index_handler.go @@ -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]))) + } +} diff --git a/mod/lock/v2/handler.go b/mod/lock/v2/handler.go new file mode 100644 index 000000000..33d25242d --- /dev/null +++ b/mod/lock/v2/handler.go @@ -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 +} diff --git a/mod/lock/v2/release_handler.go b/mod/lock/v2/release_handler.go new file mode 100644 index 000000000..998fdc51e --- /dev/null +++ b/mod/lock/v2/release_handler.go @@ -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 + } +} + diff --git a/mod/lock/v2/renew_handler.go b/mod/lock/v2/renew_handler.go new file mode 100644 index 000000000..cdd65b3aa --- /dev/null +++ b/mod/lock/v2/renew_handler.go @@ -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 + } +} diff --git a/mod/lock/v2/tests/handler_test.go b/mod/lock/v2/tests/handler_test.go new file mode 100644 index 000000000..b07572bbe --- /dev/null +++ b/mod/lock/v2/tests/handler_test.go @@ -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 +} diff --git a/mod/mod.go b/mod/mod.go index 454146221..34a380689 100644 --- a/mod/mod.go +++ b/mod/mod.go @@ -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 } diff --git a/server/config.go b/server/config.go index a8ce8d44b..214ce399d 100644 --- a/server/config.go +++ b/server/config.go @@ -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 } diff --git a/server/config_test.go b/server/config_test.go index f991c9765..002996983 100644 --- a/server/config_test.go +++ b/server/config_test.go @@ -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) { diff --git a/server/server.go b/server/server.go index 00c39227a..efc357056 100644 --- a/server/server.go +++ b/server/server.go @@ -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. diff --git a/server/usage.go b/server/usage.go index b0606ab66..0dfd1b311 100644 --- a/server/usage.go +++ b/server/usage.go @@ -31,15 +31,15 @@ Cluster Configuration Options: should match the peer's '-peer-addr' flag. Client Communication Options: - -addr= The public host:port used for client communication. - -bind-addr= The listening hostname used for client communication. - -ca-file= Path to the client CA file. - -cert-file= Path to the client cert file. - -key-file= Path to the client key file. + -addr= The public host:port used for client communication. + -bind-addr= The listening host:port used for client communication. + -ca-file= Path to the client CA file. + -cert-file= Path to the client cert file. + -key-file= Path to the client key file. Peer Communication Options: -peer-addr= The public host:port used for peer communication. - -peer-bind-addr= The listening hostname used for peer communication. + -peer-bind-addr= The listening host:port used for peer communication. -peer-ca-file= Path to the peer CA file. -peer-cert-file= Path to the peer cert file. -peer-key-file= Path to the peer key file. diff --git a/server/v2/get_handler.go b/server/v2/get_handler.go index 2f48fc32a..9a67ea2ae 100644 --- a/server/v2/get_handler.go +++ b/server/v2/get_handler.go @@ -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 } diff --git a/server/v2/tests/delete_handler_test.go b/server/v2/tests/delete_handler_test.go index c18b402f5..82d59321a 100644 --- a/server/v2/tests/delete_handler_test.go +++ b/server/v2/tests/delete_handler_test.go @@ -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}}`, "") }) } diff --git a/server/v2/tests/get_handler_test.go b/server/v2/tests/get_handler_test.go index ea0ec1189..fc930e639 100644 --- a/server/v2/tests/get_handler_test.go +++ b/server/v2/tests/get_handler_test.go @@ -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, "") }) } diff --git a/server/v2/tests/post_handler_test.go b/server/v2/tests/post_handler_test.go index c0cb23078..34b4700d7 100644 --- a/server/v2/tests/post_handler_test.go +++ b/server/v2/tests/post_handler_test.go @@ -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", "") }) } diff --git a/server/v2/tests/put_handler_test.go b/server/v2/tests/put_handler_test.go index 3a89790dd..2507f05ad 100644 --- a/server/v2/tests/put_handler_test.go +++ b/server/v2/tests/put_handler_test.go @@ -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", "") diff --git a/test.sh b/test.sh index 5cc633975..ae40d8200 100755 --- a/test.sh +++ b/test.sh @@ -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 diff --git a/tests/server_utils.go b/tests/server_utils.go index 84588efea..097764a47 100644 --- a/tests/server_utils.go +++ b/tests/server_utils.go @@ -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.