mirror of
https://github.com/etcd-io/etcd.git
synced 2024-09-27 06:25:44 +00:00
v1: deprecate v1 support
Etcd moves to 0.5 without the support of v1.
This commit is contained in:
@@ -17,7 +17,6 @@ import (
|
||||
"github.com/coreos/etcd/metrics"
|
||||
"github.com/coreos/etcd/mod"
|
||||
uhttp "github.com/coreos/etcd/pkg/http"
|
||||
"github.com/coreos/etcd/server/v1"
|
||||
"github.com/coreos/etcd/server/v2"
|
||||
"github.com/coreos/etcd/store"
|
||||
_ "github.com/coreos/etcd/store/v2"
|
||||
@@ -107,19 +106,6 @@ func (s *Server) SetStore(store store.Store) {
|
||||
s.store = store
|
||||
}
|
||||
|
||||
func (s *Server) installV1(r *mux.Router) {
|
||||
s.handleFuncV1(r, "/v1/keys/{key:.*}", v1.GetKeyHandler).Methods("GET", "HEAD")
|
||||
s.handleFuncV1(r, "/v1/keys/{key:.*}", v1.SetKeyHandler).Methods("POST", "PUT")
|
||||
s.handleFuncV1(r, "/v1/keys/{key:.*}", v1.DeleteKeyHandler).Methods("DELETE")
|
||||
s.handleFuncV1(r, "/v1/watch/{key:.*}", v1.WatchKeyHandler).Methods("GET", "HEAD", "POST")
|
||||
s.handleFunc(r, "/v1/leader", s.GetLeaderHandler).Methods("GET", "HEAD")
|
||||
s.handleFunc(r, "/v1/machines", s.GetPeersHandler).Methods("GET", "HEAD")
|
||||
s.handleFunc(r, "/v1/peers", s.GetPeersHandler).Methods("GET", "HEAD")
|
||||
s.handleFunc(r, "/v1/stats/self", s.GetStatsHandler).Methods("GET", "HEAD")
|
||||
s.handleFunc(r, "/v1/stats/leader", s.GetLeaderStatsHandler).Methods("GET", "HEAD")
|
||||
s.handleFunc(r, "/v1/stats/store", s.GetStoreStatsHandler).Methods("GET", "HEAD")
|
||||
}
|
||||
|
||||
func (s *Server) installV2(r *mux.Router) {
|
||||
r2 := mux.NewRouter()
|
||||
r.PathPrefix("/v2").Handler(ehttp.NewLowerQueryParamsHandler(r2))
|
||||
@@ -150,13 +136,6 @@ func (s *Server) installDebug(r *mux.Router) {
|
||||
r.HandleFunc("/debug/pprof/{name}", pprof.Index)
|
||||
}
|
||||
|
||||
// Adds a v1 server handler to the router.
|
||||
func (s *Server) handleFuncV1(r *mux.Router, path string, f func(http.ResponseWriter, *http.Request, v1.Server) error) *mux.Route {
|
||||
return s.handleFunc(r, path, func(w http.ResponseWriter, req *http.Request) error {
|
||||
return f(w, req, s)
|
||||
})
|
||||
}
|
||||
|
||||
// Adds a v2 server handler to the router.
|
||||
func (s *Server) handleFuncV2(r *mux.Router, path string, f func(http.ResponseWriter, *http.Request, v2.Server) error) *mux.Route {
|
||||
return s.handleFunc(r, path, func(w http.ResponseWriter, req *http.Request) error {
|
||||
@@ -202,7 +181,6 @@ func (s *Server) HTTPHandler() http.Handler {
|
||||
|
||||
// Install the routes.
|
||||
s.handleFunc(router, "/version", s.GetVersionHandler).Methods("GET")
|
||||
s.installV1(router)
|
||||
s.installV2(router)
|
||||
// Mod is deprecated temporariy due to its unstable state.
|
||||
// It would be added back later.
|
||||
@@ -235,26 +213,20 @@ func (s *Server) Dispatch(c raft.Command, w http.ResponseWriter, req *http.Reque
|
||||
return nil
|
||||
}
|
||||
|
||||
var b []byte
|
||||
if strings.HasPrefix(req.URL.Path, "/v1") {
|
||||
b, _ = json.Marshal(result.(*store.Event).Response(0))
|
||||
w.WriteHeader(http.StatusOK)
|
||||
e, _ := result.(*store.Event)
|
||||
b, _ := json.Marshal(e)
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
// etcd index should be the same as the event index
|
||||
// which is also the last modified index of the node
|
||||
w.Header().Add("X-Etcd-Index", fmt.Sprint(e.Index()))
|
||||
w.Header().Add("X-Raft-Index", fmt.Sprint(s.CommitIndex()))
|
||||
w.Header().Add("X-Raft-Term", fmt.Sprint(s.Term()))
|
||||
|
||||
if e.IsCreated() {
|
||||
w.WriteHeader(http.StatusCreated)
|
||||
} else {
|
||||
e, _ := result.(*store.Event)
|
||||
b, _ = json.Marshal(e)
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
// etcd index should be the same as the event index
|
||||
// which is also the last modified index of the node
|
||||
w.Header().Add("X-Etcd-Index", fmt.Sprint(e.Index()))
|
||||
w.Header().Add("X-Raft-Index", fmt.Sprint(s.CommitIndex()))
|
||||
w.Header().Add("X-Raft-Term", fmt.Sprint(s.Term()))
|
||||
|
||||
if e.IsCreated() {
|
||||
w.WriteHeader(http.StatusCreated)
|
||||
} else {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
}
|
||||
w.WriteHeader(http.StatusOK)
|
||||
}
|
||||
|
||||
w.Write(b)
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
package v1
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/coreos/etcd/third_party/github.com/gorilla/mux"
|
||||
)
|
||||
|
||||
// Removes a key from the store.
|
||||
func DeleteKeyHandler(w http.ResponseWriter, req *http.Request, s Server) error {
|
||||
vars := mux.Vars(req)
|
||||
key := "/" + vars["key"]
|
||||
c := s.Store().CommandFactory().CreateDeleteCommand(key, false, false)
|
||||
return s.Dispatch(c, w, req)
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
package v1
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
|
||||
"github.com/coreos/etcd/third_party/github.com/gorilla/mux"
|
||||
)
|
||||
|
||||
// Retrieves the value for a given key.
|
||||
func GetKeyHandler(w http.ResponseWriter, req *http.Request, s Server) error {
|
||||
vars := mux.Vars(req)
|
||||
key := "/" + vars["key"]
|
||||
|
||||
// Retrieve the key from the store.
|
||||
event, err := s.Store().Get(key, false, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
w.WriteHeader(http.StatusOK)
|
||||
|
||||
if req.Method == "HEAD" {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Convert event to a response and write to client.
|
||||
b, _ := json.Marshal(event.Response(s.Store().Index()))
|
||||
w.Write(b)
|
||||
return nil
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
package v1
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
etcdErr "github.com/coreos/etcd/error"
|
||||
"github.com/coreos/etcd/store"
|
||||
"github.com/coreos/etcd/third_party/github.com/goraft/raft"
|
||||
"github.com/coreos/etcd/third_party/github.com/gorilla/mux"
|
||||
)
|
||||
|
||||
// Sets the value for a given key.
|
||||
func SetKeyHandler(w http.ResponseWriter, req *http.Request, s Server) error {
|
||||
vars := mux.Vars(req)
|
||||
key := "/" + vars["key"]
|
||||
|
||||
req.ParseForm()
|
||||
|
||||
// Parse non-blank value.
|
||||
value := req.Form.Get("value")
|
||||
if len(value) == 0 {
|
||||
return etcdErr.NewError(200, "Set", s.Store().Index())
|
||||
}
|
||||
|
||||
// Convert time-to-live to an expiration time.
|
||||
expireTime, err := store.TTL(req.Form.Get("ttl"))
|
||||
if err != nil {
|
||||
return etcdErr.NewError(202, "Set", s.Store().Index())
|
||||
}
|
||||
|
||||
// If the "prevValue" is specified then test-and-set. Otherwise create a new key.
|
||||
var c raft.Command
|
||||
if prevValueArr, ok := req.Form["prevValue"]; ok {
|
||||
if len(prevValueArr[0]) > 0 {
|
||||
// test against previous value
|
||||
c = s.Store().CommandFactory().CreateCompareAndSwapCommand(key, value, prevValueArr[0], 0, expireTime)
|
||||
} else {
|
||||
// test against existence
|
||||
c = s.Store().CommandFactory().CreateCreateCommand(key, false, value, expireTime, false)
|
||||
}
|
||||
|
||||
} else {
|
||||
c = s.Store().CommandFactory().CreateSetCommand(key, false, value, expireTime)
|
||||
}
|
||||
|
||||
return s.Dispatch(c, w, req)
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
package v1
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"testing"
|
||||
|
||||
"github.com/coreos/etcd/server"
|
||||
"github.com/coreos/etcd/tests"
|
||||
"github.com/coreos/etcd/third_party/github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
// Ensures that a key is deleted.
|
||||
//
|
||||
// $ curl -X PUT localhost:4001/v1/keys/foo/bar -d value=XXX
|
||||
// $ curl -X DELETE localhost:4001/v1/keys/foo/bar
|
||||
//
|
||||
func TestV1DeleteKey(t *testing.T) {
|
||||
tests.RunServer(func(s *server.Server) {
|
||||
v := url.Values{}
|
||||
v.Set("value", "XXX")
|
||||
resp, err := tests.PutForm(fmt.Sprintf("%s%s", s.URL(), "/v1/keys/foo/bar"), v)
|
||||
tests.ReadBody(resp)
|
||||
resp, err = tests.DeleteForm(fmt.Sprintf("%s%s", s.URL(), "/v1/keys/foo/bar"), url.Values{})
|
||||
assert.Equal(t, resp.StatusCode, http.StatusOK)
|
||||
body := tests.ReadBody(resp)
|
||||
assert.Nil(t, err, "")
|
||||
assert.Equal(t, string(body), `{"action":"delete","key":"/foo/bar","prevValue":"XXX","index":4}`, "")
|
||||
})
|
||||
}
|
||||
@@ -1,209 +0,0 @@
|
||||
package v1
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/coreos/etcd/server"
|
||||
"github.com/coreos/etcd/tests"
|
||||
"github.com/coreos/etcd/third_party/github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
// Ensures that a value can be retrieve for a given key.
|
||||
//
|
||||
// $ curl localhost:4001/v1/keys/foo/bar -> fail
|
||||
// $ curl -X PUT localhost:4001/v1/keys/foo/bar -d value=XXX
|
||||
// $ curl localhost:4001/v1/keys/foo/bar
|
||||
//
|
||||
func TestV1GetKey(t *testing.T) {
|
||||
tests.RunServer(func(s *server.Server) {
|
||||
v := url.Values{}
|
||||
v.Set("value", "XXX")
|
||||
fullURL := fmt.Sprintf("%s%s", s.URL(), "/v1/keys/foo/bar")
|
||||
resp, _ := tests.Get(fullURL)
|
||||
assert.Equal(t, resp.StatusCode, http.StatusNotFound)
|
||||
|
||||
resp, _ = tests.PutForm(fullURL, v)
|
||||
tests.ReadBody(resp)
|
||||
|
||||
resp, _ = tests.Get(fullURL)
|
||||
assert.Equal(t, resp.StatusCode, http.StatusOK)
|
||||
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, "")
|
||||
})
|
||||
}
|
||||
|
||||
// Ensures that a directory of values can be retrieved for a given key.
|
||||
//
|
||||
// $ curl -X PUT localhost:4001/v1/keys/foo/x -d value=XXX
|
||||
// $ curl -X PUT localhost:4001/v1/keys/foo/y/z -d value=YYY
|
||||
// $ curl localhost:4001/v1/keys/foo
|
||||
//
|
||||
func TestV1GetKeyDir(t *testing.T) {
|
||||
tests.RunServer(func(s *server.Server) {
|
||||
v := url.Values{}
|
||||
v.Set("value", "XXX")
|
||||
resp, _ := tests.PutForm(fmt.Sprintf("%s%s", s.URL(), "/v1/keys/foo/x"), v)
|
||||
tests.ReadBody(resp)
|
||||
|
||||
v.Set("value", "YYY")
|
||||
resp, _ = tests.PutForm(fmt.Sprintf("%s%s", s.URL(), "/v1/keys/foo/y/z"), v)
|
||||
tests.ReadBody(resp)
|
||||
|
||||
resp, _ = tests.Get(fmt.Sprintf("%s%s", s.URL(), "/v1/keys/foo"))
|
||||
assert.Equal(t, resp.StatusCode, http.StatusOK)
|
||||
body := tests.ReadBody(resp)
|
||||
nodes := make([]interface{}, 0)
|
||||
if err := json.Unmarshal(body, &nodes); err != nil {
|
||||
panic(fmt.Sprintf("HTTP body JSON parse error: %v", err))
|
||||
}
|
||||
assert.Equal(t, len(nodes), 2, "")
|
||||
|
||||
node0 := nodes[0].(map[string]interface{})
|
||||
assert.Equal(t, node0["action"], "get", "")
|
||||
assert.Equal(t, node0["key"], "/foo/x", "")
|
||||
assert.Equal(t, node0["value"], "XXX", "")
|
||||
|
||||
node1 := nodes[1].(map[string]interface{})
|
||||
assert.Equal(t, node1["action"], "get", "")
|
||||
assert.Equal(t, node1["key"], "/foo/y", "")
|
||||
assert.Equal(t, node1["dir"], true, "")
|
||||
})
|
||||
}
|
||||
|
||||
// Ensures that a watcher can wait for a value to be set and return it to the client.
|
||||
//
|
||||
// $ curl localhost:4001/v1/watch/foo/bar
|
||||
// $ curl -X PUT localhost:4001/v1/keys/foo/bar -d value=XXX
|
||||
//
|
||||
func TestV1WatchKey(t *testing.T) {
|
||||
tests.RunServer(func(s *server.Server) {
|
||||
// There exists a little gap between etcd ready to serve and
|
||||
// it actually serves the first request, which means the response
|
||||
// delay could be a little bigger.
|
||||
// This test is time sensitive, so it does one request to ensure
|
||||
// that the server is working.
|
||||
tests.Get(fmt.Sprintf("%s%s", s.URL(), "/v1/keys/foo/bar"))
|
||||
|
||||
var watchResp *http.Response
|
||||
c := make(chan bool)
|
||||
go func() {
|
||||
watchResp, _ = tests.Get(fmt.Sprintf("%s%s", s.URL(), "/v1/watch/foo/bar"))
|
||||
c <- true
|
||||
}()
|
||||
|
||||
// Make sure response didn't fire early.
|
||||
time.Sleep(1 * time.Millisecond)
|
||||
|
||||
// Set a value.
|
||||
v := url.Values{}
|
||||
v.Set("value", "XXX")
|
||||
resp, _ := tests.PutForm(fmt.Sprintf("%s%s", s.URL(), "/v1/keys/foo/bar"), v)
|
||||
tests.ReadBody(resp)
|
||||
|
||||
// A response should follow from the GET above.
|
||||
time.Sleep(1 * time.Millisecond)
|
||||
|
||||
select {
|
||||
case <-c:
|
||||
|
||||
default:
|
||||
t.Fatal("cannot get watch result")
|
||||
}
|
||||
|
||||
body := tests.ReadBodyJSON(watchResp)
|
||||
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, "")
|
||||
})
|
||||
}
|
||||
|
||||
// Ensures that a watcher can wait for a value to be set after a given index.
|
||||
//
|
||||
// $ curl -X POST localhost:4001/v1/watch/foo/bar -d index=4
|
||||
// $ curl -X PUT localhost:4001/v1/keys/foo/bar -d value=XXX
|
||||
// $ curl -X PUT localhost:4001/v1/keys/foo/bar -d value=YYY
|
||||
//
|
||||
func TestV1WatchKeyWithIndex(t *testing.T) {
|
||||
tests.RunServer(func(s *server.Server) {
|
||||
var body map[string]interface{}
|
||||
c := make(chan bool)
|
||||
go func() {
|
||||
v := url.Values{}
|
||||
v.Set("index", "4")
|
||||
resp, _ := tests.PostForm(fmt.Sprintf("%s%s", s.URL(), "/v1/watch/foo/bar"), v)
|
||||
body = tests.ReadBodyJSON(resp)
|
||||
c <- true
|
||||
}()
|
||||
|
||||
// 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("%s%s", s.URL(), "/v1/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("%s%s", s.URL(), "/v1/keys/foo/bar"), v)
|
||||
tests.ReadBody(resp)
|
||||
|
||||
// A response should follow from the GET above.
|
||||
time.Sleep(1 * time.Millisecond)
|
||||
|
||||
select {
|
||||
case <-c:
|
||||
|
||||
default:
|
||||
t.Fatal("cannot get watch result")
|
||||
}
|
||||
|
||||
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, "")
|
||||
})
|
||||
}
|
||||
|
||||
// Ensures that HEAD works.
|
||||
//
|
||||
// $ curl -I localhost:4001/v1/keys/foo/bar -> fail
|
||||
// $ curl -X PUT localhost:4001/v1/keys/foo/bar -d value=XXX
|
||||
// $ curl -I localhost:4001/v1/keys/foo/bar
|
||||
//
|
||||
func TestV1HeadKey(t *testing.T) {
|
||||
tests.RunServer(func(s *server.Server) {
|
||||
v := url.Values{}
|
||||
v.Set("value", "XXX")
|
||||
fullURL := fmt.Sprintf("%s%s", s.URL(), "/v1/keys/foo/bar")
|
||||
resp, _ := tests.Get(fullURL)
|
||||
assert.Equal(t, resp.StatusCode, http.StatusNotFound)
|
||||
assert.Equal(t, resp.ContentLength, -1)
|
||||
|
||||
resp, _ = tests.PutForm(fullURL, v)
|
||||
tests.ReadBody(resp)
|
||||
|
||||
resp, _ = tests.Get(fullURL)
|
||||
assert.Equal(t, resp.StatusCode, http.StatusOK)
|
||||
assert.Equal(t, resp.ContentLength, -1)
|
||||
})
|
||||
}
|
||||
@@ -1,157 +0,0 @@
|
||||
package v1
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/coreos/etcd/server"
|
||||
"github.com/coreos/etcd/tests"
|
||||
"github.com/coreos/etcd/third_party/github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
// Ensures that a key is set to a given value.
|
||||
//
|
||||
// $ curl -X PUT localhost:4001/v1/keys/foo/bar -d value=XXX
|
||||
//
|
||||
func TestV1SetKey(t *testing.T) {
|
||||
tests.RunServer(func(s *server.Server) {
|
||||
v := url.Values{}
|
||||
v.Set("value", "XXX")
|
||||
resp, err := tests.PutForm(fmt.Sprintf("%s%s", s.URL(), "/v1/keys/foo/bar"), v)
|
||||
assert.Equal(t, resp.StatusCode, http.StatusOK)
|
||||
body := tests.ReadBody(resp)
|
||||
assert.Nil(t, err, "")
|
||||
|
||||
assert.Equal(t, string(body), `{"action":"set","key":"/foo/bar","value":"XXX","newKey":true,"index":3}`, "")
|
||||
})
|
||||
}
|
||||
|
||||
// Ensures that a time-to-live is added to a key.
|
||||
//
|
||||
// $ curl -X PUT localhost:4001/v1/keys/foo/bar -d value=XXX -d ttl=20
|
||||
//
|
||||
func TestV1SetKeyWithTTL(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("%s%s", s.URL(), "/v1/keys/foo/bar"), v)
|
||||
assert.Equal(t, resp.StatusCode, http.StatusOK)
|
||||
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/v1/keys/foo/bar -d value=XXX -d ttl=bad_ttl
|
||||
//
|
||||
func TestV1SetKeyWithBadTTL(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("%s%s", s.URL(), "/v1/keys/foo/bar"), v)
|
||||
assert.Equal(t, resp.StatusCode, http.StatusBadRequest)
|
||||
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"], "Set", "")
|
||||
})
|
||||
}
|
||||
|
||||
// Ensures that a key is conditionally set if it previously did not exist.
|
||||
//
|
||||
// $ curl -X PUT localhost:4001/v1/keys/foo/bar -d value=XXX -d prevValue=
|
||||
//
|
||||
func TestV1CreateKeySuccess(t *testing.T) {
|
||||
tests.RunServer(func(s *server.Server) {
|
||||
v := url.Values{}
|
||||
v.Set("value", "XXX")
|
||||
v.Set("prevValue", "")
|
||||
resp, _ := tests.PutForm(fmt.Sprintf("%s%s", s.URL(), "/v1/keys/foo/bar"), v)
|
||||
assert.Equal(t, resp.StatusCode, http.StatusOK)
|
||||
body := tests.ReadBodyJSON(resp)
|
||||
assert.Equal(t, body["value"], "XXX", "")
|
||||
})
|
||||
}
|
||||
|
||||
// Ensures that a key is not conditionally set because it previously existed.
|
||||
//
|
||||
// $ curl -X PUT localhost:4001/v1/keys/foo/bar -d value=XXX -d prevValue=
|
||||
// $ curl -X PUT localhost:4001/v1/keys/foo/bar -d value=XXX -d prevValue= -> fail
|
||||
//
|
||||
func TestV1CreateKeyFail(t *testing.T) {
|
||||
tests.RunServer(func(s *server.Server) {
|
||||
v := url.Values{}
|
||||
v.Set("value", "XXX")
|
||||
v.Set("prevValue", "")
|
||||
fullURL := fmt.Sprintf("%s%s", s.URL(), "/v1/keys/foo/bar")
|
||||
resp, _ := tests.PutForm(fullURL, v)
|
||||
assert.Equal(t, resp.StatusCode, http.StatusOK)
|
||||
tests.ReadBody(resp)
|
||||
resp, _ = tests.PutForm(fullURL, v)
|
||||
assert.Equal(t, resp.StatusCode, http.StatusPreconditionFailed)
|
||||
body := tests.ReadBodyJSON(resp)
|
||||
assert.Equal(t, body["errorCode"], 105, "")
|
||||
assert.Equal(t, body["message"], "Key already exists", "")
|
||||
assert.Equal(t, body["cause"], "/foo/bar", "")
|
||||
})
|
||||
}
|
||||
|
||||
// Ensures that a key is set only if the previous value matches.
|
||||
//
|
||||
// $ curl -X PUT localhost:4001/v1/keys/foo/bar -d value=XXX
|
||||
// $ curl -X PUT localhost:4001/v1/keys/foo/bar -d value=YYY -d prevValue=XXX
|
||||
//
|
||||
func TestV1SetKeyCASOnValueSuccess(t *testing.T) {
|
||||
tests.RunServer(func(s *server.Server) {
|
||||
v := url.Values{}
|
||||
v.Set("value", "XXX")
|
||||
fullURL := fmt.Sprintf("%s%s", s.URL(), "/v1/keys/foo/bar")
|
||||
resp, _ := tests.PutForm(fullURL, v)
|
||||
assert.Equal(t, resp.StatusCode, http.StatusOK)
|
||||
tests.ReadBody(resp)
|
||||
v.Set("value", "YYY")
|
||||
v.Set("prevValue", "XXX")
|
||||
resp, _ = tests.PutForm(fullURL, v)
|
||||
assert.Equal(t, resp.StatusCode, http.StatusOK)
|
||||
body := tests.ReadBodyJSON(resp)
|
||||
assert.Equal(t, body["action"], "testAndSet", "")
|
||||
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/v1/keys/foo/bar -d value=XXX
|
||||
// $ curl -X PUT localhost:4001/v1/keys/foo/bar -d value=YYY -d prevValue=AAA
|
||||
//
|
||||
func TestV1SetKeyCASOnValueFail(t *testing.T) {
|
||||
tests.RunServer(func(s *server.Server) {
|
||||
v := url.Values{}
|
||||
v.Set("value", "XXX")
|
||||
fullURL := fmt.Sprintf("%s%s", s.URL(), "/v1/keys/foo/bar")
|
||||
resp, _ := tests.PutForm(fullURL, v)
|
||||
assert.Equal(t, resp.StatusCode, http.StatusOK)
|
||||
tests.ReadBody(resp)
|
||||
v.Set("value", "YYY")
|
||||
v.Set("prevValue", "AAA")
|
||||
resp, _ = tests.PutForm(fullURL, v)
|
||||
assert.Equal(t, resp.StatusCode, http.StatusPreconditionFailed)
|
||||
body := tests.ReadBodyJSON(resp)
|
||||
assert.Equal(t, body["errorCode"], 101, "")
|
||||
assert.Equal(t, body["message"], "Compare failed", "")
|
||||
assert.Equal(t, body["cause"], "[AAA != XXX]", "")
|
||||
assert.Equal(t, body["index"], 3, "")
|
||||
})
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
package v1
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/coreos/etcd/store"
|
||||
"github.com/coreos/etcd/third_party/github.com/goraft/raft"
|
||||
)
|
||||
|
||||
// The Server interface provides all the methods required for the v1 API.
|
||||
type Server interface {
|
||||
CommitIndex() uint64
|
||||
Term() uint64
|
||||
Store() store.Store
|
||||
Dispatch(raft.Command, http.ResponseWriter, *http.Request) error
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
package v1
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
etcdErr "github.com/coreos/etcd/error"
|
||||
"github.com/coreos/etcd/third_party/github.com/gorilla/mux"
|
||||
)
|
||||
|
||||
// Watches a given key prefix for changes.
|
||||
func WatchKeyHandler(w http.ResponseWriter, req *http.Request, s Server) error {
|
||||
var err error
|
||||
vars := mux.Vars(req)
|
||||
key := "/" + vars["key"]
|
||||
|
||||
// Create a command to watch from a given index (default 0).
|
||||
var sinceIndex uint64 = 0
|
||||
if req.Method == "POST" {
|
||||
sinceIndex, err = strconv.ParseUint(string(req.FormValue("index")), 10, 64)
|
||||
if err != nil {
|
||||
return etcdErr.NewError(203, "Watch From Index", s.Store().Index())
|
||||
}
|
||||
}
|
||||
|
||||
// Start the watcher on the store.
|
||||
watcher, err := s.Store().Watch(key, false, false, sinceIndex)
|
||||
if err != nil {
|
||||
return etcdErr.NewError(500, key, s.Store().Index())
|
||||
}
|
||||
event := <-watcher.EventChan
|
||||
|
||||
// Convert event to a response and write to client.
|
||||
w.WriteHeader(http.StatusOK)
|
||||
if req.Method == "HEAD" {
|
||||
return nil
|
||||
}
|
||||
b, _ := json.Marshal(event.Response(s.Store().Index()))
|
||||
w.Write(b)
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user