diff --git a/etcdserver/etcdhttp/http.go b/etcdserver/etcdhttp/http.go index 13f8e9ff7..71393ecb6 100644 --- a/etcdserver/etcdhttp/http.go +++ b/etcdserver/etcdhttp/http.go @@ -40,11 +40,14 @@ import ( ) const ( + // prefixes of client endpoint keysPrefix = "/v2/keys" deprecatedMachinesPrefix = "/v2/machines" adminMembersPrefix = "/v2/admin/members/" - raftPrefix = "/raft" statsPrefix = "/v2/stats" + // prefixes of peer endpoint + raftPrefix = "/raft" + membersPrefix = "/members" // time to wait for response from EtcdServer requests defaultServerTimeout = 500 * time.Millisecond @@ -90,6 +93,7 @@ func NewPeerHandler(server *etcdserver.EtcdServer) http.Handler { } mux := http.NewServeMux() mux.HandleFunc(raftPrefix, sh.serveRaft) + mux.HandleFunc(membersPrefix, sh.serveMembers) mux.HandleFunc("/", http.NotFound) return mux } @@ -300,6 +304,13 @@ func (h serverHandler) serveRaft(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusNoContent) } +func (h serverHandler) serveMembers(w http.ResponseWriter, r *http.Request) { + if !allowMethod(w, r.Method, "GET") { + return + } + h.serveAdminMembers(w, r) +} + // parseKeyRequest converts a received http.Request on keysPrefix to // a server Request, performing validation of supplied fields as appropriate. // If any validation fails, an empty Request and non-nil error is returned. diff --git a/etcdserver/etcdhttp/http_test.go b/etcdserver/etcdhttp/http_test.go index 30c44942b..2cbe84391 100644 --- a/etcdserver/etcdhttp/http_test.go +++ b/etcdserver/etcdhttp/http_test.go @@ -989,6 +989,34 @@ func TestServeRaft(t *testing.T) { } } +func TestServeMembersFails(t *testing.T) { + tests := []struct { + method string + wcode int + }{ + { + "POST", + http.StatusMethodNotAllowed, + }, + { + "DELETE", + http.StatusMethodNotAllowed, + }, + { + "BAD", + http.StatusMethodNotAllowed, + }, + } + for i, tt := range tests { + h := &serverHandler{} + rw := httptest.NewRecorder() + h.serveMembers(rw, &http.Request{Method: tt.method}) + if rw.Code != tt.wcode { + t.Errorf("#%d: code=%d, want %d", i, rw.Code, tt.wcode) + } + } +} + // resServer implements the etcd.Server interface for testing. // It returns the given responsefrom any Do calls, and nil error type resServer struct { @@ -1533,7 +1561,7 @@ func (s *serverRecorder) RemoveMember(_ context.Context, id uint64) error { return nil } -func TestServeAdminMembersGet(t *testing.T) { +func TestServeAdminMembersAndMembersGet(t *testing.T) { memb1 := etcdserver.Member{ID: 1, Attributes: etcdserver.Attributes{ClientURLs: []string{"http://localhost:8080"}}} memb2 := etcdserver.Member{ID: 2, Attributes: etcdserver.Attributes{ClientURLs: []string{"http://localhost:8081"}}} cluster := &fakeCluster{ @@ -1567,22 +1595,26 @@ func TestServeAdminMembersGet(t *testing.T) { {path.Join(adminMembersPrefix, "100"), http.StatusNotFound, "text/plain; charset=utf-8", "member not found\n"}, } - for i, tt := range tests { - req, err := http.NewRequest("GET", mustNewURL(t, tt.path).String(), nil) - if err != nil { - t.Fatal(err) - } - rw := httptest.NewRecorder() - h.serveAdminMembers(rw, req) + funcs := []func(w http.ResponseWriter, r *http.Request){h.serveAdminMembers, h.serveMembers} - if rw.Code != tt.wcode { - t.Errorf("#%d: code=%d, want %d", i, rw.Code, tt.wcode) - } - if gct := rw.Header().Get("Content-Type"); gct != tt.wct { - t.Errorf("#%d: content-type = %s, want %s", i, gct, tt.wct) - } - if rw.Body.String() != tt.wbody { - t.Errorf("#%d: body = %s, want %s", i, rw.Body.String(), tt.wbody) + for i, tt := range tests { + for j, f := range funcs { + req, err := http.NewRequest("GET", mustNewURL(t, tt.path).String(), nil) + if err != nil { + t.Fatal(err) + } + rw := httptest.NewRecorder() + f(rw, req) + + if rw.Code != tt.wcode { + t.Errorf("#%d.%d: code=%d, want %d", i, j, rw.Code, tt.wcode) + } + if gct := rw.Header().Get("Content-Type"); gct != tt.wct { + t.Errorf("#%d.%d: content-type = %s, want %s", i, j, gct, tt.wct) + } + if rw.Body.String() != tt.wbody { + t.Errorf("#%d.%d: body = %s, want %s", i, j, rw.Body.String(), tt.wbody) + } } } }