refactor move http status logic to server.go

This commit is contained in:
Xiang Li
2013-10-20 10:21:54 -07:00
30 changed files with 2744 additions and 50 deletions

View File

@@ -7,6 +7,7 @@ import (
"encoding/json"
"fmt"
"io/ioutil"
"net"
"net/http"
"net/url"
"time"
@@ -20,6 +21,8 @@ import (
type PeerServer struct {
raftServer raft.Server
server *Server
httpServer *http.Server
listener net.Listener
joinIndex uint64
name string
url string
@@ -88,7 +91,7 @@ func NewPeerServer(name string, path string, url string, listenHost string, tlsC
}
// Start the raft server
func (s *PeerServer) ListenAndServe(snapshot bool, cluster []string) {
func (s *PeerServer) ListenAndServe(snapshot bool, cluster []string) error {
// LoadSnapshot
if snapshot {
err := s.raftServer.LoadSnapshot()
@@ -137,8 +140,60 @@ func (s *PeerServer) ListenAndServe(snapshot bool, cluster []string) {
}
// start to response to raft requests
go s.startTransport(s.tlsConf.Scheme, s.tlsConf.Server)
return s.startTransport(s.tlsConf.Scheme, s.tlsConf.Server)
}
// Overridden version of net/http added so we can manage the listener.
func (s *PeerServer) listenAndServe() error {
addr := s.httpServer.Addr
if addr == "" {
addr = ":http"
}
l, e := net.Listen("tcp", addr)
if e != nil {
return e
}
s.listener = l
return s.httpServer.Serve(l)
}
// Overridden version of net/http added so we can manage the listener.
func (s *PeerServer) listenAndServeTLS(certFile, keyFile string) error {
addr := s.httpServer.Addr
if addr == "" {
addr = ":https"
}
config := &tls.Config{}
if s.httpServer.TLSConfig != nil {
*config = *s.httpServer.TLSConfig
}
if config.NextProtos == nil {
config.NextProtos = []string{"http/1.1"}
}
var err error
config.Certificates = make([]tls.Certificate, 1)
config.Certificates[0], err = tls.LoadX509KeyPair(certFile, keyFile)
if err != nil {
return err
}
conn, err := net.Listen("tcp", addr)
if err != nil {
return err
}
tlsListener := tls.NewListener(conn, config)
s.listener = tlsListener
return s.httpServer.Serve(tlsListener)
}
// Stops the server.
func (s *PeerServer) Close() {
if s.listener != nil {
s.listener.Close()
s.listener = nil
}
}
// Retrieves the underlying Raft server.
@@ -177,12 +232,12 @@ func (s *PeerServer) startAsFollower(cluster []string) {
}
// Start to listen and response raft command
func (s *PeerServer) startTransport(scheme string, tlsConf tls.Config) {
func (s *PeerServer) startTransport(scheme string, tlsConf tls.Config) error {
log.Infof("raft server [name %s, listen on %s, advertised url %s]", s.name, s.listenHost, s.url)
raftMux := http.NewServeMux()
server := &http.Server{
s.httpServer = &http.Server{
Handler: raftMux,
TLSConfig: &tlsConf,
Addr: s.listenHost,
@@ -201,9 +256,9 @@ func (s *PeerServer) startTransport(scheme string, tlsConf tls.Config) {
raftMux.HandleFunc("/etcdURL", s.EtcdURLHttpHandler)
if scheme == "http" {
log.Fatal(server.ListenAndServe())
return s.listenAndServe()
} else {
log.Fatal(server.ListenAndServeTLS(s.tlsInfo.CertFile, s.tlsInfo.KeyFile))
return s.listenAndServeTLS(s.tlsInfo.CertFile, s.tlsInfo.KeyFile)
}
}

View File

@@ -1,8 +1,10 @@
package server
import (
"crypto/tls"
"encoding/json"
"fmt"
"net"
"net/http"
"net/url"
"strings"
@@ -22,6 +24,7 @@ type Server struct {
http.Server
peerServer *PeerServer
registry *Registry
listener net.Listener
store store.Store
name string
url string
@@ -157,13 +160,66 @@ func (s *Server) handleFunc(path string, f func(http.ResponseWriter, *http.Reque
}
// Start to listen and response etcd client command
func (s *Server) ListenAndServe() {
func (s *Server) ListenAndServe() error {
log.Infof("etcd server [name %s, listen on %s, advertised url %s]", s.name, s.Server.Addr, s.url)
if s.tlsConf.Scheme == "http" {
log.Fatal(s.Server.ListenAndServe())
return s.listenAndServe()
} else {
log.Fatal(s.Server.ListenAndServeTLS(s.tlsInfo.CertFile, s.tlsInfo.KeyFile))
return s.listenAndServeTLS(s.tlsInfo.CertFile, s.tlsInfo.KeyFile)
}
}
// Overridden version of net/http added so we can manage the listener.
func (s *Server) listenAndServe() error {
addr := s.Server.Addr
if addr == "" {
addr = ":http"
}
l, e := net.Listen("tcp", addr)
if e != nil {
return e
}
s.listener = l
return s.Server.Serve(l)
}
// Overridden version of net/http added so we can manage the listener.
func (s *Server) listenAndServeTLS(certFile, keyFile string) error {
addr := s.Server.Addr
if addr == "" {
addr = ":https"
}
config := &tls.Config{}
if s.Server.TLSConfig != nil {
*config = *s.Server.TLSConfig
}
if config.NextProtos == nil {
config.NextProtos = []string{"http/1.1"}
}
var err error
config.Certificates = make([]tls.Certificate, 1)
config.Certificates[0], err = tls.LoadX509KeyPair(certFile, keyFile)
if err != nil {
return err
}
conn, err := net.Listen("tcp", addr)
if err != nil {
return err
}
tlsListener := tls.NewListener(conn, config)
s.listener = tlsListener
return s.Server.Serve(tlsListener)
}
// Stops the server.
func (s *Server) Close() {
if s.listener != nil {
s.listener.Close()
s.listener = nil
}
}
@@ -193,7 +249,12 @@ func (s *Server) Dispatch(c raft.Command, w http.ResponseWriter, req *http.Reque
} else {
e, _ := result.(*store.Event)
b, _ = json.Marshal(e)
w.WriteHeader(e.HttpStatusCode())
if e.IsCreated() {
w.WriteHeader(http.StatusCreated)
} else {
w.WriteHeader(http.StatusOK)
}
}
w.Write(b)

View File

@@ -12,6 +12,8 @@ import (
)
func PutHandler(w http.ResponseWriter, req *http.Request, s Server) error {
var c raft.Command
vars := mux.Vars(req)
key := "/" + vars["key"]
@@ -23,11 +25,14 @@ func PutHandler(w http.ResponseWriter, req *http.Request, s Server) error {
return etcdErr.NewError(etcdErr.EcodeTTLNaN, "Update", store.UndefIndex, store.UndefTerm)
}
prevValue, valueOk := req.Form["prevValue"]
prevIndexStr, indexOk := req.Form["prevIndex"]
prevExist, existOk := req.Form["prevExist"]
_, valueOk := req.Form["prevValue"]
prevValue := req.Form.Get("prevValue")
var c raft.Command
_, indexOk := req.Form["prevIndex"]
prevIndexStr := req.Form.Get("prevIndex")
_, existOk := req.Form["prevExist"]
prevExist := req.Form.Get("prevExist")
// Set handler: create a new node or replace the old one.
if !valueOk && !indexOk && !existOk {
@@ -36,13 +41,13 @@ func PutHandler(w http.ResponseWriter, req *http.Request, s Server) error {
// update with test
if existOk {
if prevExist[0] == "false" {
if prevExist == "false" {
// Create command: create a new node. Fail, if a node already exists
// Ignore prevIndex and prevValue
return CreateHandler(w, req, s, key, value, expireTime)
}
if prevExist[0] == "true" && !indexOk && !valueOk {
if prevExist == "true" && !indexOk && !valueOk {
return UpdateHandler(w, req, s, key, value, expireTime)
}
}
@@ -50,7 +55,7 @@ func PutHandler(w http.ResponseWriter, req *http.Request, s Server) error {
var prevIndex uint64
if indexOk {
prevIndex, err = strconv.ParseUint(prevIndexStr[0], 10, 64)
prevIndex, err = strconv.ParseUint(prevIndexStr, 10, 64)
// bad previous index
if err != nil {
@@ -61,7 +66,7 @@ func PutHandler(w http.ResponseWriter, req *http.Request, s Server) error {
}
if valueOk {
if prevValue[0] == "" {
if prevValue == "" {
return etcdErr.NewError(etcdErr.EcodePrevValueRequired, "CompareAndSwap", store.UndefIndex, store.UndefTerm)
}
}
@@ -69,7 +74,7 @@ func PutHandler(w http.ResponseWriter, req *http.Request, s Server) error {
c = &store.CompareAndSwapCommand{
Key: key,
Value: value,
PrevValue: prevValue[0],
PrevValue: prevValue,
PrevIndex: prevIndex,
}

View File

@@ -0,0 +1,152 @@
package v2
import (
"fmt"
"net/url"
"testing"
"time"
"github.com/coreos/etcd/server"
"github.com/coreos/etcd/tests"
"github.com/stretchr/testify/assert"
)
// Ensures that a value can be retrieve for a given key.
//
// $ curl -X PUT localhost:4001/v2/keys/foo/bar -d value=XXX
// $ curl localhost:4001/v2/keys/foo/bar
//
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)
tests.ReadBody(resp)
resp, _ = tests.Get(fmt.Sprintf("http://%s%s", s.URL(), "/v2/keys/foo/bar"))
body := tests.ReadBodyJSON(resp)
assert.Equal(t, body["action"], "get", "")
assert.Equal(t, body["key"], "/foo/bar", "")
assert.Equal(t, body["value"], "XXX", "")
assert.Equal(t, body["index"], 3, "")
assert.Equal(t, body["term"], 0, "")
})
}
// Ensures that a directory of values can be recursively retrieved for a given key.
//
// $ curl -X PUT localhost:4001/v2/keys/foo/x -d value=XXX
// $ curl -X PUT localhost:4001/v2/keys/foo/y/z -d value=YYY
// $ curl localhost:4001/v2/keys/foo -d recursive=true
//
func TestV2GetKeyRecursively(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/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)
tests.ReadBody(resp)
resp, _ = tests.Get(fmt.Sprintf("http://%s%s", s.URL(), "/v2/keys/foo?recursive=true"))
body := tests.ReadBodyJSON(resp)
assert.Equal(t, body["action"], "get", "")
assert.Equal(t, body["key"], "/foo", "")
assert.Equal(t, body["dir"], true, "")
assert.Equal(t, body["index"], 4, "")
assert.Equal(t, len(body["kvs"].([]interface{})), 2, "")
kv0 := body["kvs"].([]interface{})[0].(map[string]interface{})
assert.Equal(t, kv0["key"], "/foo/x", "")
assert.Equal(t, kv0["value"], "XXX", "")
kv1 := body["kvs"].([]interface{})[1].(map[string]interface{})
assert.Equal(t, kv1["key"], "/foo/y", "")
assert.Equal(t, kv1["dir"], true, "")
kvs2 := kv1["kvs"].([]interface{})[0].(map[string]interface{})
assert.Equal(t, kvs2["key"], "/foo/y/z", "")
assert.Equal(t, kvs2["value"], "YYY", "")
})
}
// Ensures that a watcher can wait for a value to be set and return it to the client.
//
// $ curl localhost:4001/v2/keys/foo/bar?wait=true
// $ curl -X PUT localhost:4001/v2/keys/foo/bar -d value=XXX
//
func TestV2WatchKey(t *testing.T) {
tests.RunServer(func(s *server.Server) {
var body map[string]interface{}
go func() {
resp, _ := tests.Get(fmt.Sprintf("http://%s%s", s.URL(), "/v2/keys/foo/bar?wait=true"))
body = tests.ReadBodyJSON(resp)
}()
// Make sure response didn't fire early.
time.Sleep(1 * time.Millisecond)
assert.Nil(t, body, "")
// 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)
tests.ReadBody(resp)
// A response should follow from the GET above.
time.Sleep(1 * time.Millisecond)
assert.NotNil(t, body, "")
assert.Equal(t, body["action"], "set", "")
assert.Equal(t, body["key"], "/foo/bar", "")
assert.Equal(t, body["value"], "XXX", "")
assert.Equal(t, body["index"], 3, "")
assert.Equal(t, body["term"], 0, "")
})
}
// Ensures that a watcher can wait for a value to be set after a given index.
//
// $ curl localhost:4001/v2/keys/foo/bar?wait=true&waitIndex=4
// $ curl -X PUT localhost:4001/v2/keys/foo/bar -d value=XXX
// $ curl -X PUT localhost:4001/v2/keys/foo/bar -d value=YYY
//
func TestV2WatchKeyWithIndex(t *testing.T) {
tests.RunServer(func(s *server.Server) {
var body map[string]interface{}
go func() {
resp, _ := tests.Get(fmt.Sprintf("http://%s%s", s.URL(), "/v2/keys/foo/bar?wait=true&waitIndex=5"))
body = tests.ReadBodyJSON(resp)
}()
// Make sure response didn't fire early.
time.Sleep(1 * time.Millisecond)
assert.Nil(t, body, "")
// 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)
tests.ReadBody(resp)
// Make sure response didn't fire early.
time.Sleep(1 * time.Millisecond)
assert.Nil(t, body, "")
// 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)
tests.ReadBody(resp)
// A response should follow from the GET above.
time.Sleep(1 * time.Millisecond)
assert.NotNil(t, body, "")
assert.Equal(t, body["action"], "set", "")
assert.Equal(t, body["key"], "/foo/bar", "")
assert.Equal(t, body["value"], "YYY", "")
assert.Equal(t, body["index"], 4, "")
assert.Equal(t, body["term"], 0, "")
})
}

View File

@@ -0,0 +1,38 @@
package v2
import (
"fmt"
"testing"
"github.com/coreos/etcd/server"
"github.com/coreos/etcd/tests"
"github.com/stretchr/testify/assert"
)
// Ensures a unique value is added to the key's children.
//
// $ curl -X POST localhost:4001/v2/keys/foo/bar
// $ curl -X POST localhost:4001/v2/keys/foo/bar
// $ curl -X POST localhost:4001/v2/keys/foo/baz
//
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)
body := tests.ReadBodyJSON(resp)
assert.Equal(t, body["action"], "create", "")
assert.Equal(t, body["key"], "/foo/bar/3", "")
assert.Equal(t, body["dir"], true, "")
assert.Equal(t, body["index"], 3, "")
// Second POST should add next index to list.
resp, _ = tests.PostForm(fmt.Sprintf("http://%s%s", s.URL(), "/v2/keys/foo/bar"), nil)
body = tests.ReadBodyJSON(resp)
assert.Equal(t, body["key"], "/foo/bar/4", "")
// 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)
body = tests.ReadBodyJSON(resp)
assert.Equal(t, body["key"], "/foo/baz/5", "")
})
}

View File

@@ -0,0 +1,280 @@
package v2
import (
"fmt"
"net/url"
"testing"
"time"
"github.com/coreos/etcd/server"
"github.com/coreos/etcd/tests"
"github.com/stretchr/testify/assert"
)
// Ensures that a key is set to a given value.
//
// $ curl -X PUT localhost:4001/v2/keys/foo/bar -d value=XXX
//
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)
body := tests.ReadBody(resp)
assert.Nil(t, err, "")
assert.Equal(t, string(body), `{"action":"set","key":"/foo/bar","value":"XXX","index":3,"term":0}`, "")
})
}
// Ensures that a time-to-live is added to a key.
//
// $ curl -X PUT localhost:4001/v2/keys/foo/bar -d value=XXX -d ttl=20
//
func TestV2SetKeyWithTTL(t *testing.T) {
tests.RunServer(func(s *server.Server) {
t0 := time.Now()
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)
body := tests.ReadBodyJSON(resp)
assert.Equal(t, body["ttl"], 20, "")
// Make sure the expiration date is correct.
expiration, _ := time.Parse(time.RFC3339Nano, body["expiration"].(string))
assert.Equal(t, expiration.Sub(t0) / time.Second, 20, "")
})
}
// Ensures that an invalid time-to-live is returned as an error.
//
// $ curl -X PUT localhost:4001/v2/keys/foo/bar -d value=XXX -d ttl=bad_ttl
//
func TestV2SetKeyWithBadTTL(t *testing.T) {
tests.RunServer(func(s *server.Server) {
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)
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", "")
assert.Equal(t, body["cause"], "Update", "")
})
}
// Ensures that a key is conditionally set only if it previously did not exist.
//
// $ curl -X PUT localhost:4001/v2/keys/foo/bar -d value=XXX -d prevExist=false
//
func TestV2CreateKeySuccess(t *testing.T) {
tests.RunServer(func(s *server.Server) {
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)
body := tests.ReadBodyJSON(resp)
assert.Equal(t, body["value"], "XXX", "")
})
}
// Ensures that a key is not conditionally because it previously existed.
//
// $ curl -X PUT localhost:4001/v2/keys/foo/bar -d value=XXX
// $ curl -X PUT localhost:4001/v2/keys/foo/bar -d value=XXX -d prevExist=false
//
func TestV2CreateKeyFail(t *testing.T) {
tests.RunServer(func(s *server.Server) {
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)
tests.ReadBody(resp)
resp, _ = tests.PutForm(fmt.Sprintf("http://%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", "")
assert.Equal(t, body["cause"], "/foo/bar", "")
})
}
// Ensures that a key is conditionally set only if it previously did exist.
//
// $ curl -X PUT localhost:4001/v2/keys/foo/bar -d value=XXX
// $ curl -X PUT localhost:4001/v2/keys/foo/bar -d value=YYY -d prevExist=true
//
func TestV2UpdateKeySuccess(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)
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)
body := tests.ReadBodyJSON(resp)
assert.Equal(t, body["action"], "update", "")
assert.Equal(t, body["prevValue"], "XXX", "")
})
}
// Ensures that a key is not conditionally set if it previously did not exist.
//
// $ curl -X PUT localhost:4001/v2/keys/foo/bar -d value=XXX -d prevExist=true
//
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)
v.Set("value", "YYY")
v.Set("prevExist", "true")
resp, _ = tests.PutForm(fmt.Sprintf("http://%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", "")
assert.Equal(t, body["cause"], "/foo/bar", "")
})
}
// Ensures that a key is not conditionally set if it previously did not exist.
//
// $ curl -X PUT localhost:4001/v2/keys/foo -d value=XXX -d prevExist=true
// $ curl -X PUT localhost:4001/v2/keys/foo/bar -d value=XXX -d prevExist=true
//
func TestV2UpdateKeyFailOnMissingDirectory(t *testing.T) {
tests.RunServer(func(s *server.Server) {
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)
body := tests.ReadBodyJSON(resp)
assert.Equal(t, body["errorCode"], 100, "")
assert.Equal(t, body["message"], "Key Not Found", "")
assert.Equal(t, body["cause"], "/foo", "")
})
}
// Ensures that a key is set only if the previous index matches.
//
// $ curl -X PUT localhost:4001/v2/keys/foo/bar -d value=XXX
// $ curl -X PUT localhost:4001/v2/keys/foo/bar -d value=YYY -d prevIndex=3
//
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)
tests.ReadBody(resp)
v.Set("value", "YYY")
v.Set("prevIndex", "3")
resp, _ = tests.PutForm(fmt.Sprintf("http://%s%s", s.URL(), "/v2/keys/foo/bar"), v)
body := tests.ReadBodyJSON(resp)
assert.Equal(t, body["action"], "compareAndSwap", "")
assert.Equal(t, body["prevValue"], "XXX", "")
assert.Equal(t, body["value"], "YYY", "")
assert.Equal(t, body["index"], 4, "")
})
}
// Ensures that a key is not set if the previous index does not match.
//
// $ curl -X PUT localhost:4001/v2/keys/foo/bar -d value=XXX
// $ curl -X PUT localhost:4001/v2/keys/foo/bar -d value=YYY -d prevIndex=10
//
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)
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)
body := tests.ReadBodyJSON(resp)
assert.Equal(t, body["errorCode"], 101, "")
assert.Equal(t, body["message"], "Test Failed", "")
assert.Equal(t, body["cause"], "[ != XXX] [10 != 3]", "")
assert.Equal(t, body["index"], 4, "")
})
}
// Ensures that an error is thrown if an invalid previous index is provided.
//
// $ curl -X PUT localhost:4001/v2/keys/foo/bar -d value=YYY -d prevIndex=bad_index
//
func TestV2SetKeyCASWithInvalidIndex(t *testing.T) {
tests.RunServer(func(s *server.Server) {
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)
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", "")
assert.Equal(t, body["cause"], "CompareAndSwap", "")
})
}
// Ensures that a key is set only if the previous value matches.
//
// $ curl -X PUT localhost:4001/v2/keys/foo/bar -d value=XXX
// $ curl -X PUT localhost:4001/v2/keys/foo/bar -d value=YYY -d prevValue=XXX
//
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)
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)
body := tests.ReadBodyJSON(resp)
assert.Equal(t, body["action"], "compareAndSwap", "")
assert.Equal(t, body["prevValue"], "XXX", "")
assert.Equal(t, body["value"], "YYY", "")
assert.Equal(t, body["index"], 4, "")
})
}
// Ensures that a key is not set if the previous value does not match.
//
// $ curl -X PUT localhost:4001/v2/keys/foo/bar -d value=XXX
// $ curl -X PUT localhost:4001/v2/keys/foo/bar -d value=YYY -d prevValue=AAA
//
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)
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)
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 != 3]", "")
assert.Equal(t, body["index"], 4, "")
})
}
// Ensures that an error is returned if a blank prevValue is set.
//
// $ curl -X PUT localhost:4001/v2/keys/foo/bar -d value=XXX -d prevValue=
//
func TestV2SetKeyCASWithMissingValueFails(t *testing.T) {
tests.RunServer(func(s *server.Server) {
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)
body := tests.ReadBodyJSON(resp)
assert.Equal(t, body["errorCode"], 201, "")
assert.Equal(t, body["message"], "PrevValue is Required in POST form", "")
assert.Equal(t, body["cause"], "CompareAndSwap", "")
})
}