|
@@ -2,6 +2,7 @@ package etcdhttp
|
|
|
|
|
|
|
|
import (
|
|
import (
|
|
|
"bytes"
|
|
"bytes"
|
|
|
|
|
+ "encoding/json"
|
|
|
"errors"
|
|
"errors"
|
|
|
"io"
|
|
"io"
|
|
|
"net/http"
|
|
"net/http"
|
|
@@ -33,10 +34,11 @@ func mustNewURL(t *testing.T, s string) *url.URL {
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// mustNewRequest takes a path, appends it to the standard keysPrefix, and constructs
|
|
// mustNewRequest takes a path, appends it to the standard keysPrefix, and constructs
|
|
|
-// an *http.Request referencing the resulting URL
|
|
|
|
|
|
|
+// a GET *http.Request referencing the resulting URL
|
|
|
func mustNewRequest(t *testing.T, p string) *http.Request {
|
|
func mustNewRequest(t *testing.T, p string) *http.Request {
|
|
|
return &http.Request{
|
|
return &http.Request{
|
|
|
- URL: mustNewURL(t, path.Join(keysPrefix, p)),
|
|
|
|
|
|
|
+ Method: "GET",
|
|
|
|
|
+ URL: mustNewURL(t, path.Join(keysPrefix, p)),
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -191,8 +193,9 @@ func TestGoodParseRequest(t *testing.T) {
|
|
|
// good prefix, all other values default
|
|
// good prefix, all other values default
|
|
|
mustNewRequest(t, "foo"),
|
|
mustNewRequest(t, "foo"),
|
|
|
etcdserverpb.Request{
|
|
etcdserverpb.Request{
|
|
|
- Id: 1234,
|
|
|
|
|
- Path: "/foo",
|
|
|
|
|
|
|
+ Id: 1234,
|
|
|
|
|
+ Method: "GET",
|
|
|
|
|
+ Path: "/foo",
|
|
|
},
|
|
},
|
|
|
},
|
|
},
|
|
|
{
|
|
{
|
|
@@ -434,8 +437,9 @@ func TestWriteEvent(t *testing.T) {
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
tests := []struct {
|
|
tests := []struct {
|
|
|
- ev *store.Event
|
|
|
|
|
- idx string
|
|
|
|
|
|
|
+ ev *store.Event
|
|
|
|
|
+ idx string
|
|
|
|
|
+ // TODO(jonboulle): check body as well as just status code
|
|
|
code int
|
|
code int
|
|
|
err error
|
|
err error
|
|
|
}{
|
|
}{
|
|
@@ -782,21 +786,65 @@ func mustMarshalMsg(t *testing.T, m raftpb.Message) []byte {
|
|
|
|
|
|
|
|
func TestServeRaft(t *testing.T) {
|
|
func TestServeRaft(t *testing.T) {
|
|
|
testCases := []struct {
|
|
testCases := []struct {
|
|
|
- reqBody io.Reader
|
|
|
|
|
|
|
+ method string
|
|
|
|
|
+ body io.Reader
|
|
|
serverErr error
|
|
serverErr error
|
|
|
- wcode int
|
|
|
|
|
|
|
+
|
|
|
|
|
+ wcode int
|
|
|
}{
|
|
}{
|
|
|
{
|
|
{
|
|
|
|
|
+ // bad method
|
|
|
|
|
+ "GET",
|
|
|
|
|
+ bytes.NewReader(
|
|
|
|
|
+ mustMarshalMsg(
|
|
|
|
|
+ t,
|
|
|
|
|
+ raftpb.Message{},
|
|
|
|
|
+ ),
|
|
|
|
|
+ ),
|
|
|
|
|
+ nil,
|
|
|
|
|
+ http.StatusMethodNotAllowed,
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ // bad method
|
|
|
|
|
+ "PUT",
|
|
|
|
|
+ bytes.NewReader(
|
|
|
|
|
+ mustMarshalMsg(
|
|
|
|
|
+ t,
|
|
|
|
|
+ raftpb.Message{},
|
|
|
|
|
+ ),
|
|
|
|
|
+ ),
|
|
|
|
|
+ nil,
|
|
|
|
|
+ http.StatusMethodNotAllowed,
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ // bad method
|
|
|
|
|
+ "DELETE",
|
|
|
|
|
+ bytes.NewReader(
|
|
|
|
|
+ mustMarshalMsg(
|
|
|
|
|
+ t,
|
|
|
|
|
+ raftpb.Message{},
|
|
|
|
|
+ ),
|
|
|
|
|
+ ),
|
|
|
|
|
+ nil,
|
|
|
|
|
+ http.StatusMethodNotAllowed,
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ // bad request body
|
|
|
|
|
+ "POST",
|
|
|
&errReader{},
|
|
&errReader{},
|
|
|
nil,
|
|
nil,
|
|
|
http.StatusBadRequest,
|
|
http.StatusBadRequest,
|
|
|
},
|
|
},
|
|
|
{
|
|
{
|
|
|
|
|
+ // bad request protobuf
|
|
|
|
|
+ "POST",
|
|
|
strings.NewReader("malformed garbage"),
|
|
strings.NewReader("malformed garbage"),
|
|
|
nil,
|
|
nil,
|
|
|
http.StatusBadRequest,
|
|
http.StatusBadRequest,
|
|
|
},
|
|
},
|
|
|
{
|
|
{
|
|
|
|
|
+ // good request, etcdserver.Server error
|
|
|
|
|
+ "POST",
|
|
|
bytes.NewReader(
|
|
bytes.NewReader(
|
|
|
mustMarshalMsg(
|
|
mustMarshalMsg(
|
|
|
t,
|
|
t,
|
|
@@ -807,6 +855,8 @@ func TestServeRaft(t *testing.T) {
|
|
|
http.StatusInternalServerError,
|
|
http.StatusInternalServerError,
|
|
|
},
|
|
},
|
|
|
{
|
|
{
|
|
|
|
|
+ // good request
|
|
|
|
|
+ "POST",
|
|
|
bytes.NewReader(
|
|
bytes.NewReader(
|
|
|
mustMarshalMsg(
|
|
mustMarshalMsg(
|
|
|
t,
|
|
t,
|
|
@@ -818,7 +868,7 @@ func TestServeRaft(t *testing.T) {
|
|
|
},
|
|
},
|
|
|
}
|
|
}
|
|
|
for i, tt := range testCases {
|
|
for i, tt := range testCases {
|
|
|
- req, err := http.NewRequest("POST", "foo", tt.reqBody)
|
|
|
|
|
|
|
+ req, err := http.NewRequest(tt.method, "foo", tt.body)
|
|
|
if err != nil {
|
|
if err != nil {
|
|
|
t.Fatalf("#%d: could not create request: %#v", i, err)
|
|
t.Fatalf("#%d: could not create request: %#v", i, err)
|
|
|
}
|
|
}
|
|
@@ -834,3 +884,184 @@ func TestServeRaft(t *testing.T) {
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
|
|
+// resServer implements the etcd.Server interface for testing.
|
|
|
|
|
+// It returns the given responsefrom any Do calls, and nil error
|
|
|
|
|
+type resServer struct {
|
|
|
|
|
+ res etcdserver.Response
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+func (rs *resServer) Do(_ context.Context, _ etcdserverpb.Request) (etcdserver.Response, error) {
|
|
|
|
|
+ return rs.res, nil
|
|
|
|
|
+}
|
|
|
|
|
+func (rs *resServer) Process(_ context.Context, _ raftpb.Message) error { return nil }
|
|
|
|
|
+func (rs *resServer) Start() {}
|
|
|
|
|
+func (rs *resServer) Stop() {}
|
|
|
|
|
+
|
|
|
|
|
+func mustMarshalEvent(t *testing.T, ev *store.Event) string {
|
|
|
|
|
+ b := new(bytes.Buffer)
|
|
|
|
|
+ if err := json.NewEncoder(b).Encode(ev); err != nil {
|
|
|
|
|
+ t.Fatalf("error marshalling event %#v: %v", ev, err)
|
|
|
|
|
+ }
|
|
|
|
|
+ return b.String()
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+func TestBadServeKeys(t *testing.T) {
|
|
|
|
|
+ testBadCases := []struct {
|
|
|
|
|
+ req *http.Request
|
|
|
|
|
+ server etcdserver.Server
|
|
|
|
|
+
|
|
|
|
|
+ wcode int
|
|
|
|
|
+ }{
|
|
|
|
|
+ {
|
|
|
|
|
+ // bad method
|
|
|
|
|
+ &http.Request{
|
|
|
|
|
+ Method: "CONNECT",
|
|
|
|
|
+ },
|
|
|
|
|
+ &resServer{},
|
|
|
|
|
+
|
|
|
|
|
+ http.StatusMethodNotAllowed,
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ // bad method
|
|
|
|
|
+ &http.Request{
|
|
|
|
|
+ Method: "TRACE",
|
|
|
|
|
+ },
|
|
|
|
|
+ &resServer{},
|
|
|
|
|
+
|
|
|
|
|
+ http.StatusMethodNotAllowed,
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ // parseRequest error
|
|
|
|
|
+ &http.Request{
|
|
|
|
|
+ Body: nil,
|
|
|
|
|
+ Method: "PUT",
|
|
|
|
|
+ },
|
|
|
|
|
+ &resServer{},
|
|
|
|
|
+
|
|
|
|
|
+ http.StatusBadRequest,
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ // etcdserver.Server error
|
|
|
|
|
+ mustNewRequest(t, "foo"),
|
|
|
|
|
+ &errServer{
|
|
|
|
|
+ errors.New("blah"),
|
|
|
|
|
+ },
|
|
|
|
|
+
|
|
|
|
|
+ http.StatusInternalServerError,
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ // timeout waiting for event (watcher never returns)
|
|
|
|
|
+ mustNewRequest(t, "foo"),
|
|
|
|
|
+ &resServer{
|
|
|
|
|
+ etcdserver.Response{
|
|
|
|
|
+ Watcher: &dummyWatcher{},
|
|
|
|
|
+ },
|
|
|
|
|
+ },
|
|
|
|
|
+
|
|
|
|
|
+ http.StatusGatewayTimeout,
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ // non-event/watcher response from etcdserver.Server
|
|
|
|
|
+ mustNewRequest(t, "foo"),
|
|
|
|
|
+ &resServer{
|
|
|
|
|
+ etcdserver.Response{},
|
|
|
|
|
+ },
|
|
|
|
|
+
|
|
|
|
|
+ http.StatusInternalServerError,
|
|
|
|
|
+ },
|
|
|
|
|
+ }
|
|
|
|
|
+ for i, tt := range testBadCases {
|
|
|
|
|
+ h := &serverHandler{
|
|
|
|
|
+ timeout: 0, // context times out immediately
|
|
|
|
|
+ server: tt.server,
|
|
|
|
|
+ peers: nil,
|
|
|
|
|
+ }
|
|
|
|
|
+ rw := httptest.NewRecorder()
|
|
|
|
|
+ h.serveKeys(rw, tt.req)
|
|
|
|
|
+ if rw.Code != tt.wcode {
|
|
|
|
|
+ t.Errorf("#%d: got code=%d, want %d", i, rw.Code, tt.wcode)
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+func TestServeKeysEvent(t *testing.T) {
|
|
|
|
|
+ req := mustNewRequest(t, "foo")
|
|
|
|
|
+ server := &resServer{
|
|
|
|
|
+ etcdserver.Response{
|
|
|
|
|
+ Event: &store.Event{
|
|
|
|
|
+ Action: store.Get,
|
|
|
|
|
+ Node: &store.NodeExtern{},
|
|
|
|
|
+ },
|
|
|
|
|
+ },
|
|
|
|
|
+ }
|
|
|
|
|
+ h := &serverHandler{
|
|
|
|
|
+ timeout: time.Hour,
|
|
|
|
|
+ server: server,
|
|
|
|
|
+ peers: nil,
|
|
|
|
|
+ }
|
|
|
|
|
+ rw := httptest.NewRecorder()
|
|
|
|
|
+
|
|
|
|
|
+ h.serveKeys(rw, req)
|
|
|
|
|
+
|
|
|
|
|
+ wcode := http.StatusOK
|
|
|
|
|
+ wbody := mustMarshalEvent(
|
|
|
|
|
+ t,
|
|
|
|
|
+ &store.Event{
|
|
|
|
|
+ Action: store.Get,
|
|
|
|
|
+ Node: &store.NodeExtern{},
|
|
|
|
|
+ },
|
|
|
|
|
+ )
|
|
|
|
|
+
|
|
|
|
|
+ if rw.Code != wcode {
|
|
|
|
|
+ t.Errorf("got code=%d, want %d", rw.Code, wcode)
|
|
|
|
|
+ }
|
|
|
|
|
+ g := rw.Body.String()
|
|
|
|
|
+ if g != wbody {
|
|
|
|
|
+ t.Errorf("got body=%#v, want %#v", g, wbody)
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+func TestServeKeysWatch(t *testing.T) {
|
|
|
|
|
+ req := mustNewRequest(t, "/foo/bar")
|
|
|
|
|
+ ec := make(chan *store.Event)
|
|
|
|
|
+ dw := &dummyWatcher{
|
|
|
|
|
+ echan: ec,
|
|
|
|
|
+ }
|
|
|
|
|
+ server := &resServer{
|
|
|
|
|
+ etcdserver.Response{
|
|
|
|
|
+ Watcher: dw,
|
|
|
|
|
+ },
|
|
|
|
|
+ }
|
|
|
|
|
+ h := &serverHandler{
|
|
|
|
|
+ timeout: time.Hour,
|
|
|
|
|
+ server: server,
|
|
|
|
|
+ peers: nil,
|
|
|
|
|
+ }
|
|
|
|
|
+ go func() {
|
|
|
|
|
+ ec <- &store.Event{
|
|
|
|
|
+ Action: store.Get,
|
|
|
|
|
+ Node: &store.NodeExtern{},
|
|
|
|
|
+ }
|
|
|
|
|
+ }()
|
|
|
|
|
+ rw := httptest.NewRecorder()
|
|
|
|
|
+
|
|
|
|
|
+ h.serveKeys(rw, req)
|
|
|
|
|
+
|
|
|
|
|
+ wcode := http.StatusOK
|
|
|
|
|
+ wbody := mustMarshalEvent(
|
|
|
|
|
+ t,
|
|
|
|
|
+ &store.Event{
|
|
|
|
|
+ Action: store.Get,
|
|
|
|
|
+ Node: &store.NodeExtern{},
|
|
|
|
|
+ },
|
|
|
|
|
+ )
|
|
|
|
|
+
|
|
|
|
|
+ if rw.Code != wcode {
|
|
|
|
|
+ t.Errorf("got code=%d, want %d", rw.Code, wcode)
|
|
|
|
|
+ }
|
|
|
|
|
+ g := rw.Body.String()
|
|
|
|
|
+ if g != wbody {
|
|
|
|
|
+ t.Errorf("got body=%#v, want %#v", g, wbody)
|
|
|
|
|
+ }
|
|
|
|
|
+}
|