diff --git a/Documentation/clients-matrix.md b/Documentation/clients-matrix.md index 469e1ef4f..fa6659ad0 100644 --- a/Documentation/clients-matrix.md +++ b/Documentation/clients-matrix.md @@ -1,7 +1,6 @@ # Client libraries support matrix for etcd As etcd features support is really uneven between client libraries, a compatibility matrix can be important. -We will consider in detail only the features of clients supporting the v2 API. Clients still supporting the v1 API *only* are listed below. ## v2 clients @@ -14,6 +13,7 @@ The v2 API has a lot of features, we will categorize them in a few categories: - **GET,PUT,POST,DEL Features**: Support for all the modifiers when calling the etcd server with said HTTP method. ### Supported features matrix + **Legend** **F**: Full support **G**: Good support **B**: Basic support **Y**: Feature supported **-**: Feature not supported @@ -30,6 +30,7 @@ Sorted alphabetically on language/name |[go-etcd](https://github.com/coreos/go-etcd) |go |Y|Y|F|F|F|F|-|-| |[etcd4j](https://github.com/jurmous/etcd4j) |java |Y|Y|F|F|F|F|-|-| |[jetcd](https://github.com/diwakergupta/jetcd) |java |Y|-|B|B|-|B|-|-| +|[jetcd](https://github.com/justinsb/jetcd) |java |-|-|B|B|-|B|-|-| |[Etcd.jl](https://github.com/forio/Etcd.jl) |Julia |-|-|F|F|F|F|Y|Y| |[etcetera](https://github.com/drusellers/etcetera) |.net |-|-|F|F|F|F|-|-| |[node-etcd](https://github.com/stianeikeland/node-etcd) |nodejs |Y|-|F|F|-|F|-|-| @@ -37,15 +38,5 @@ Sorted alphabetically on language/name |[p5-etcd](https://metacpan.org/release/Etcd) |perl |-|-|F|F|F|F|-|-| |[python-etcd](https://github.com/jplana/python-etcd) |python |Y|Y|F|F|F|F|Y|-| |[python-etcd-client](https://github.com/dsoprea/PythonEtcdClient)|python |Y|Y|F|F|F|F|Y|Y| +|[txetcd](https://github.com/russellhaering/txetcd) |python |-|-|G|G|F|G|-|-| |[etcd-ruby](https://github.com/ranjib/etcd-ruby) |ruby |-|-|F|F|F|F|-|-| - -## v1-only clients - -Clients supporting only the API version 1 - -- [justinsb/jetcd](https://github.com/justinsb/jetcd) Java -- [transitorykris/etcd-py](https://github.com/transitorykris/etcd-py) Python -- [russellhaering/txetcd](https://github.com/russellhaering/txetcd) Python -- [iconara/etcd-rb](https://github.com/iconara/etcd-rb) Ruby -- [jpfuentes2/etcd-ruby](https://github.com/jpfuentes2/etcd-ruby) Ruby -- [marshall-lee/etcd.erl](https://github.com/marshall-lee/etcd.erl) Erlang diff --git a/README.md b/README.md index 99c5adbba..c95baf31e 100644 --- a/README.md +++ b/README.md @@ -120,10 +120,10 @@ curl -L http://127.0.0.1:4001/version #### API Versioning -Clients are encouraged to use the `v2` API. The `v1` API will not change. - The `v2` API responses should not change after the 0.2.0 release but new features will be added over time. +The `v1` API has been deprecated and will not be supported. + During the pre-v1.0.0 series of releases we may break the API as we fix bugs and get feedback. #### 32bit systems diff --git a/server/server.go b/server/server.go index 394a9bc32..9d82d889c 100644 --- a/server/server.go +++ b/server/server.go @@ -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) diff --git a/server/v1/delete_key_handler.go b/server/v1/delete_key_handler.go deleted file mode 100644 index fd147601d..000000000 --- a/server/v1/delete_key_handler.go +++ /dev/null @@ -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) -} diff --git a/server/v1/get_key_handler.go b/server/v1/get_key_handler.go deleted file mode 100644 index 541480fe5..000000000 --- a/server/v1/get_key_handler.go +++ /dev/null @@ -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 -} diff --git a/server/v1/set_key_handler.go b/server/v1/set_key_handler.go deleted file mode 100644 index fa27db2a1..000000000 --- a/server/v1/set_key_handler.go +++ /dev/null @@ -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) -} diff --git a/server/v1/tests/delete_handler_test.go b/server/v1/tests/delete_handler_test.go deleted file mode 100644 index 437e40e93..000000000 --- a/server/v1/tests/delete_handler_test.go +++ /dev/null @@ -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}`, "") - }) -} diff --git a/server/v1/tests/get_handler_test.go b/server/v1/tests/get_handler_test.go deleted file mode 100644 index 6e045f1a5..000000000 --- a/server/v1/tests/get_handler_test.go +++ /dev/null @@ -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) - }) -} diff --git a/server/v1/tests/put_handler_test.go b/server/v1/tests/put_handler_test.go deleted file mode 100644 index f7aeb2e6c..000000000 --- a/server/v1/tests/put_handler_test.go +++ /dev/null @@ -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, "") - }) -} diff --git a/server/v1/v1.go b/server/v1/v1.go deleted file mode 100644 index e0c7dc5ff..000000000 --- a/server/v1/v1.go +++ /dev/null @@ -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 -} diff --git a/server/v1/watch_key_handler.go b/server/v1/watch_key_handler.go deleted file mode 100644 index 7d4d7ada0..000000000 --- a/server/v1/watch_key_handler.go +++ /dev/null @@ -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 -} diff --git a/store/event.go b/store/event.go index 84ebf3ce3..5d702ec3b 100644 --- a/store/event.go +++ b/store/event.go @@ -45,54 +45,3 @@ func (e *Event) IsCreated() bool { func (e *Event) Index() uint64 { return e.Node.ModifiedIndex } - -// Converts an event object into a response object. -func (event *Event) Response(currentIndex uint64) interface{} { - if !event.Node.Dir { - response := &Response{ - Action: event.Action, - Key: event.Node.Key, - Value: event.Node.Value, - Index: event.Node.ModifiedIndex, - TTL: event.Node.TTL, - Expiration: event.Node.Expiration, - } - - if event.PrevNode != nil { - response.PrevValue = event.PrevNode.Value - } - - if currentIndex != 0 { - response.Index = currentIndex - } - - if response.Action == Set { - if response.PrevValue == nil { - response.NewKey = true - } - } - - if response.Action == CompareAndSwap || response.Action == Create { - response.Action = "testAndSet" - } - - return response - } else { - responses := make([]*Response, len(event.Node.Nodes)) - - for i, node := range event.Node.Nodes { - responses[i] = &Response{ - Action: event.Action, - Key: node.Key, - Value: node.Value, - Dir: node.Dir, - Index: node.ModifiedIndex, - } - - if currentIndex != 0 { - responses[i].Index = currentIndex - } - } - return responses - } -} diff --git a/store/response_v1.go b/store/response_v1.go deleted file mode 100644 index 5b9244bf9..000000000 --- a/store/response_v1.go +++ /dev/null @@ -1,26 +0,0 @@ -package store - -import ( - "time" -) - -// The response from the store to the user who issue a command -type Response struct { - Action string `json:"action"` - Key string `json:"key"` - Dir bool `json:"dir,omitempty"` - PrevValue *string `json:"prevValue,omitempty"` - Value *string `json:"value,omitempty"` - - // If the key did not exist before the action, - // this field should be set to true - NewKey bool `json:"newKey,omitempty"` - - Expiration *time.Time `json:"expiration,omitempty"` - - // Time to live in second - TTL int64 `json:"ttl,omitempty"` - - // The command index of the raft machine when the command is executed - Index uint64 `json:"index"` -} diff --git a/test.sh b/test.sh index 9a7af6fde..5cabb52a1 100755 --- a/test.sh +++ b/test.sh @@ -17,9 +17,6 @@ go test -v ./server -race go test -i ./config go test -v ./config -race -go test -i ./server/v1/tests -go test -v ./server/v1/tests -race - go test -i ./server/v2/tests go test -v ./server/v2/tests -race diff --git a/tests/fixtures/v1.cluster/README b/tests/fixtures/v1.cluster/README deleted file mode 100644 index 8e144444a..000000000 --- a/tests/fixtures/v1.cluster/README +++ /dev/null @@ -1,15 +0,0 @@ -README - -The scripts in this directory should be run from the project root: - -$ cd $GOPATH/src/github.com/coreos/etcd -$ tests/fixtures/v1/run.1.sh - -Scripts with numbers should be run in separate terminal windows (in order): - -$ tests/fixtures/v1/run.1.sh -$ tests/fixtures/v1/run.2.sh -$ tests/fixtures/v1/run.3.sh -$ tests/fixtures/v1/run.4.sh - -The resulting server state data can be found in tmp/node*. diff --git a/tests/fixtures/v1.cluster/node0/conf b/tests/fixtures/v1.cluster/node0/conf deleted file mode 100644 index 8f401dbe4..000000000 --- a/tests/fixtures/v1.cluster/node0/conf +++ /dev/null @@ -1 +0,0 @@ -{"commitIndex":15,"peers":[{"name":"node2","connectionString":""}]} \ No newline at end of file diff --git a/tests/fixtures/v1.cluster/node0/info b/tests/fixtures/v1.cluster/node0/info deleted file mode 100644 index 398c8e1e8..000000000 --- a/tests/fixtures/v1.cluster/node0/info +++ /dev/null @@ -1,18 +0,0 @@ -{ - "name": "node0", - "raftURL": "http://127.0.0.1:7001", - "etcdURL": "http://127.0.0.1:4001", - "webURL": "", - "raftListenHost": "127.0.0.1:7001", - "etcdListenHost": "127.0.0.1:4001", - "raftTLS": { - "CertFile": "", - "KeyFile": "", - "CAFile": "" - }, - "etcdTLS": { - "CertFile": "", - "KeyFile": "", - "CAFile": "" - } -} diff --git a/tests/fixtures/v1.cluster/node0/log b/tests/fixtures/v1.cluster/node0/log deleted file mode 100644 index de3e7075e..000000000 Binary files a/tests/fixtures/v1.cluster/node0/log and /dev/null differ diff --git a/tests/fixtures/v1.cluster/node2/conf b/tests/fixtures/v1.cluster/node2/conf deleted file mode 100644 index 19d6a9c92..000000000 --- a/tests/fixtures/v1.cluster/node2/conf +++ /dev/null @@ -1 +0,0 @@ -{"commitIndex":15,"peers":[{"name":"node0","connectionString":""}]} \ No newline at end of file diff --git a/tests/fixtures/v1.cluster/node2/info b/tests/fixtures/v1.cluster/node2/info deleted file mode 100644 index 85114a5f8..000000000 --- a/tests/fixtures/v1.cluster/node2/info +++ /dev/null @@ -1,18 +0,0 @@ -{ - "name": "node2", - "raftURL": "http://127.0.0.1:7002", - "etcdURL": "http://127.0.0.1:4002", - "webURL": "", - "raftListenHost": "127.0.0.1:7002", - "etcdListenHost": "127.0.0.1:4002", - "raftTLS": { - "CertFile": "", - "KeyFile": "", - "CAFile": "" - }, - "etcdTLS": { - "CertFile": "", - "KeyFile": "", - "CAFile": "" - } -} diff --git a/tests/fixtures/v1.cluster/node2/log b/tests/fixtures/v1.cluster/node2/log deleted file mode 100644 index de3e7075e..000000000 Binary files a/tests/fixtures/v1.cluster/node2/log and /dev/null differ diff --git a/tests/fixtures/v1.cluster/node3/conf b/tests/fixtures/v1.cluster/node3/conf deleted file mode 100644 index d8a5840de..000000000 --- a/tests/fixtures/v1.cluster/node3/conf +++ /dev/null @@ -1 +0,0 @@ -{"commitIndex":15,"peers":[{"name":"node0","connectionString":""},{"name":"node2","connectionString":""}]} \ No newline at end of file diff --git a/tests/fixtures/v1.cluster/node3/info b/tests/fixtures/v1.cluster/node3/info deleted file mode 100644 index 5e5cb3f3a..000000000 --- a/tests/fixtures/v1.cluster/node3/info +++ /dev/null @@ -1,18 +0,0 @@ -{ - "name": "node3", - "raftURL": "http://127.0.0.1:7003", - "etcdURL": "http://127.0.0.1:4003", - "webURL": "", - "raftListenHost": "127.0.0.1:7003", - "etcdListenHost": "127.0.0.1:4003", - "raftTLS": { - "CertFile": "", - "KeyFile": "", - "CAFile": "" - }, - "etcdTLS": { - "CertFile": "", - "KeyFile": "", - "CAFile": "" - } -} diff --git a/tests/fixtures/v1.cluster/node3/log b/tests/fixtures/v1.cluster/node3/log deleted file mode 100644 index de3e7075e..000000000 Binary files a/tests/fixtures/v1.cluster/node3/log and /dev/null differ diff --git a/tests/fixtures/v1.cluster/run.1.sh b/tests/fixtures/v1.cluster/run.1.sh deleted file mode 100755 index ee77deaed..000000000 --- a/tests/fixtures/v1.cluster/run.1.sh +++ /dev/null @@ -1,4 +0,0 @@ -#!/bin/sh - -./build -./etcd -d tmp/node0 -n node0 diff --git a/tests/fixtures/v1.cluster/run.2.sh b/tests/fixtures/v1.cluster/run.2.sh deleted file mode 100755 index 1b067eb2b..000000000 --- a/tests/fixtures/v1.cluster/run.2.sh +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/sh - -./etcd -s 127.0.0.1:7002 -c 127.0.0.1:4002 -C 127.0.0.1:7001 -d tmp/node2 -n node2 diff --git a/tests/fixtures/v1.cluster/run.3.sh b/tests/fixtures/v1.cluster/run.3.sh deleted file mode 100755 index a1c9c6b3e..000000000 --- a/tests/fixtures/v1.cluster/run.3.sh +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/sh - -./etcd -s 127.0.0.1:7003 -c 127.0.0.1:4003 -C 127.0.0.1:7001 -d tmp/node3 -n node3 diff --git a/tests/fixtures/v1.cluster/run.4.sh b/tests/fixtures/v1.cluster/run.4.sh deleted file mode 100755 index 15c756eb0..000000000 --- a/tests/fixtures/v1.cluster/run.4.sh +++ /dev/null @@ -1,13 +0,0 @@ -#!/bin/sh - -curl -L http://127.0.0.1:4001/v1/keys/message -d value="Hello world" -curl -L http://127.0.0.1:4001/v1/keys/message -d value="Hello etcd" -curl -L http://127.0.0.1:4001/v1/keys/message -X DELETE -curl -L http://127.0.0.1:4001/v1/keys/message2 -d value="Hola" -curl -L http://127.0.0.1:4001/v1/keys/expiring -d value=bar -d ttl=5 -curl -L http://127.0.0.1:4001/v1/keys/foo -d value=one -curl -L http://127.0.0.1:4001/v1/keys/foo -d prevValue=two -d value=three -curl -L http://127.0.0.1:4001/v1/keys/foo -d prevValue=one -d value=two -curl -L http://127.0.0.1:4001/v1/keys/bar -d prevValue= -d value=four -curl -L http://127.0.0.1:4001/v1/keys/bar -d prevValue= -d value=five -curl -X DELETE http://127.0.0.1:7001/remove/node2 diff --git a/tests/fixtures/v1.solo/README b/tests/fixtures/v1.solo/README deleted file mode 100644 index 65d86d323..000000000 --- a/tests/fixtures/v1.solo/README +++ /dev/null @@ -1,13 +0,0 @@ -README - -The scripts in this directory should be run from the project root: - -$ cd $GOPATH/src/github.com/coreos/etcd -$ tests/fixtures/v1.solo/run.1.sh - -Scripts with numbers should be run in separate terminal windows (in order): - -$ tests/fixtures/v1/run.1.sh -$ tests/fixtures/v1/run.2.sh - -The resulting server state data can be found in tmp/node0. diff --git a/tests/fixtures/v1.solo/node0/conf b/tests/fixtures/v1.solo/node0/conf deleted file mode 100644 index 95106f8b1..000000000 --- a/tests/fixtures/v1.solo/node0/conf +++ /dev/null @@ -1 +0,0 @@ -{"commitIndex":1,"peers":[]} \ No newline at end of file diff --git a/tests/fixtures/v1.solo/node0/info b/tests/fixtures/v1.solo/node0/info deleted file mode 100644 index 398c8e1e8..000000000 --- a/tests/fixtures/v1.solo/node0/info +++ /dev/null @@ -1,18 +0,0 @@ -{ - "name": "node0", - "raftURL": "http://127.0.0.1:7001", - "etcdURL": "http://127.0.0.1:4001", - "webURL": "", - "raftListenHost": "127.0.0.1:7001", - "etcdListenHost": "127.0.0.1:4001", - "raftTLS": { - "CertFile": "", - "KeyFile": "", - "CAFile": "" - }, - "etcdTLS": { - "CertFile": "", - "KeyFile": "", - "CAFile": "" - } -} diff --git a/tests/fixtures/v1.solo/node0/log b/tests/fixtures/v1.solo/node0/log deleted file mode 100644 index 661d21d0e..000000000 Binary files a/tests/fixtures/v1.solo/node0/log and /dev/null differ diff --git a/tests/fixtures/v1.solo/run.1.sh b/tests/fixtures/v1.solo/run.1.sh deleted file mode 100755 index ee77deaed..000000000 --- a/tests/fixtures/v1.solo/run.1.sh +++ /dev/null @@ -1,4 +0,0 @@ -#!/bin/sh - -./build -./etcd -d tmp/node0 -n node0 diff --git a/tests/fixtures/v1.solo/run.2.sh b/tests/fixtures/v1.solo/run.2.sh deleted file mode 100755 index 96bd3e862..000000000 --- a/tests/fixtures/v1.solo/run.2.sh +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/sh - -curl -L http://127.0.0.1:4001/v1/keys/message -d value="Hello world" diff --git a/tests/functional/util.go b/tests/functional/util.go index 135ee14e2..36a72c39a 100644 --- a/tests/functional/util.go +++ b/tests/functional/util.go @@ -227,7 +227,7 @@ func Monitor(size int, allowDeadNum int, leaderChan chan string, all chan bool, func getLeader(addr string) (string, error) { - resp, err := client.Get(addr + "/v1/leader") + resp, err := client.Get(addr + "/v2/leader") if err != nil { return "", err diff --git a/tests/functional/v1_migration_test.go b/tests/functional/v1_migration_test.go deleted file mode 100644 index 75aaebb3f..000000000 --- a/tests/functional/v1_migration_test.go +++ /dev/null @@ -1,106 +0,0 @@ -package test - -import ( - "fmt" - "io/ioutil" - "net/http" - "os" - "os/exec" - "path/filepath" - "testing" - "time" - - "github.com/coreos/etcd/tests" - "github.com/coreos/etcd/third_party/github.com/stretchr/testify/assert" -) - -// Ensure that we can start a v2 node from the log of a v1 node. -func TestV1SoloMigration(t *testing.T) { - path, _ := ioutil.TempDir("", "etcd-") - os.MkdirAll(path, 0777) - defer os.RemoveAll(path) - - nodepath := filepath.Join(path, "node0") - fixturepath, _ := filepath.Abs("../fixtures/v1.solo/node0") - fmt.Println("DATA_DIR =", nodepath) - - // Copy over fixture files. - c := exec.Command("cp", "-rf", fixturepath, nodepath) - if out, err := c.CombinedOutput(); err != nil { - fmt.Println(">>>>>>\n", string(out), "<<<<<<") - panic("Fixture initialization error:" + err.Error()) - } - - procAttr := new(os.ProcAttr) - procAttr.Files = []*os.File{nil, os.Stdout, os.Stderr} - - args := []string{"etcd", fmt.Sprintf("-data-dir=%s", nodepath)} - args = append(args, "-addr", "127.0.0.1:4001") - args = append(args, "-peer-addr", "127.0.0.1:7001") - args = append(args, "-name", "node0") - process, err := os.StartProcess(EtcdBinPath, args, procAttr) - if err != nil { - t.Fatal("start process failed:" + err.Error()) - return - } - defer process.Kill() - time.Sleep(time.Second) - - // Ensure deleted message is removed. - resp, err := tests.Get("http://localhost:4001/v2/keys/message") - tests.ReadBody(resp) - assert.Nil(t, err, "") - assert.Equal(t, resp.StatusCode, 200, "") -} - -// Ensure that we can start a v2 cluster from the logs of a v1 cluster. -func TestV1ClusterMigration(t *testing.T) { - path, _ := ioutil.TempDir("", "etcd-") - os.RemoveAll(path) - defer os.RemoveAll(path) - - nodes := []string{"node0", "node2"} - for i, node := range nodes { - nodepath := filepath.Join(path, node) - fixturepath, _ := filepath.Abs(filepath.Join("../fixtures/v1.cluster/", node)) - fmt.Println("FIXPATH =", fixturepath) - fmt.Println("NODEPATH =", nodepath) - os.MkdirAll(filepath.Dir(nodepath), 0777) - - // Copy over fixture files. - c := exec.Command("cp", "-rf", fixturepath, nodepath) - if out, err := c.CombinedOutput(); err != nil { - fmt.Println(">>>>>>\n", string(out), "<<<<<<") - panic("Fixture initialization error:" + err.Error()) - } - - procAttr := new(os.ProcAttr) - procAttr.Files = []*os.File{nil, os.Stdout, os.Stderr} - - args := []string{"etcd", fmt.Sprintf("-data-dir=%s", nodepath)} - args = append(args, "-addr", fmt.Sprintf("127.0.0.1:%d", 4001+i)) - args = append(args, "-peer-addr", fmt.Sprintf("127.0.0.1:%d", 7001+i)) - args = append(args, "-name", node) - process, err := os.StartProcess(EtcdBinPath, args, procAttr) - if err != nil { - t.Fatal("start process failed:" + err.Error()) - return - } - defer process.Kill() - time.Sleep(time.Second) - } - - // Ensure deleted message is removed. - resp, err := tests.Get("http://localhost:4001/v2/keys/message") - body := tests.ReadBody(resp) - assert.Nil(t, err, "") - assert.Equal(t, resp.StatusCode, http.StatusNotFound) - assert.Equal(t, string(body), `{"errorCode":100,"message":"Key not found","cause":"/message","index":11}`+"\n") - - // Ensure TTL'd message is removed. - resp, err = tests.Get("http://localhost:4001/v2/keys/foo") - body = tests.ReadBody(resp) - assert.Nil(t, err, "") - assert.Equal(t, resp.StatusCode, 200, "") - assert.Equal(t, string(body), `{"action":"get","node":{"key":"/foo","value":"one","modifiedIndex":9,"createdIndex":9}}`) -}