v1: deprecate v1 support

Etcd moves to 0.5 without the support of v1.
This commit is contained in:
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

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