v1: deprecate v1 support

Etcd moves to 0.5 without the support of v1.
release-2.0
Yicheng Qin 2014-07-02 11:17:55 -07:00
parent 8d758be3e4
commit 02ced2c2d7
36 changed files with 19 additions and 924 deletions

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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)
}

View File

@ -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
}

View File

@ -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)
}

View File

@ -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}`, "")
})
}

View File

@ -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)
})
}

View File

@ -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, "")
})
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}
}

View File

@ -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"`
}

View File

@ -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

View File

@ -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*.

View File

@ -1 +0,0 @@
{"commitIndex":15,"peers":[{"name":"node2","connectionString":""}]}

View File

@ -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": ""
}
}

Binary file not shown.

View File

@ -1 +0,0 @@
{"commitIndex":15,"peers":[{"name":"node0","connectionString":""}]}

View File

@ -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": ""
}
}

Binary file not shown.

View File

@ -1 +0,0 @@
{"commitIndex":15,"peers":[{"name":"node0","connectionString":""},{"name":"node2","connectionString":""}]}

View File

@ -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": ""
}
}

Binary file not shown.

View File

@ -1,4 +0,0 @@
#!/bin/sh
./build
./etcd -d tmp/node0 -n node0

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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.

View File

@ -1 +0,0 @@
{"commitIndex":1,"peers":[]}

View File

@ -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": ""
}
}

Binary file not shown.

View File

@ -1,4 +0,0 @@
#!/bin/sh
./build
./etcd -d tmp/node0 -n node0

View File

@ -1,3 +0,0 @@
#!/bin/sh
curl -L http://127.0.0.1:4001/v1/keys/message -d value="Hello world"

View File

@ -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

View File

@ -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}}`)
}