Merge pull request #1365 from xiangli-cmu/admin_get

etcdserver: support GET on admin endpoint
This commit is contained in:
Xiang Li 2014-10-23 11:30:51 -07:00
commit 16c9970a03
2 changed files with 103 additions and 1 deletions

View File

@ -25,6 +25,7 @@ import (
"net/http"
"net/url"
"path"
"sort"
"strconv"
"strings"
"time"
@ -149,13 +150,43 @@ func (h serverHandler) serveMachines(w http.ResponseWriter, r *http.Request) {
}
func (h serverHandler) serveAdminMembers(w http.ResponseWriter, r *http.Request) {
if !allowMethod(w, r.Method, "POST", "DELETE") {
if !allowMethod(w, r.Method, "GET", "POST", "DELETE") {
return
}
ctx, cancel := context.WithTimeout(context.Background(), defaultServerTimeout)
defer cancel()
switch r.Method {
case "GET":
idStr := strings.TrimPrefix(r.URL.Path, adminMembersPrefix)
if idStr == "" {
msmap := h.clusterStore.Get().Members()
ms := make(SortableMemberSlice, 0, len(msmap))
for _, m := range msmap {
ms = append(ms, m)
}
sort.Sort(ms)
w.Header().Set("Content-Type", "application/json")
if err := json.NewEncoder(w).Encode(ms); err != nil {
log.Printf("etcdhttp: %v", err)
}
return
}
id, err := strconv.ParseUint(idStr, 16, 64)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
m := h.clusterStore.Get().FindID(id)
if m == nil {
http.Error(w, "member not found", http.StatusNotFound)
return
}
w.Header().Set("Content-Type", "application/json")
if err := json.NewEncoder(w).Encode(m); err != nil {
log.Printf("etcdhttp: %v", err)
}
return
case "POST":
ctype := r.Header.Get("Content-Type")
if ctype != "application/json" {
@ -551,3 +582,9 @@ func trimNodeExternPrefix(n *store.NodeExtern, prefix string) *store.NodeExtern
}
return n
}
type SortableMemberSlice []*etcdserver.Member
func (s SortableMemberSlice) Len() int { return len(s) }
func (s SortableMemberSlice) Less(i, j int) bool { return s[i].ID < s[j].ID }
func (s SortableMemberSlice) Swap(i, j int) { s[i], s[j] = s[j], s[i] }

View File

@ -1485,6 +1485,16 @@ func TestServeAdminMembersFail(t *testing.T) {
http.StatusInternalServerError,
},
{
// etcdserver.GetMember bad id
&http.Request{
URL: mustNewURL(t, path.Join(adminMembersPrefix, "badid")),
Method: "GET",
},
&errServer{},
http.StatusBadRequest,
},
}
for i, tt := range tests {
h := &serverHandler{
@ -1527,6 +1537,61 @@ func (s *serverRecorder) RemoveMember(_ context.Context, id uint64) error {
return nil
}
func TestServeAdminMembersGet(t *testing.T) {
cluster := &fakeCluster{
members: []etcdserver.Member{
{ID: 1, Attributes: etcdserver.Attributes{ClientURLs: []string{"http://localhost:8080"}}},
{ID: 2, Attributes: etcdserver.Attributes{ClientURLs: []string{"http://localhost:8081"}}},
},
}
h := &serverHandler{
server: &serverRecorder{},
clock: clockwork.NewFakeClock(),
clusterStore: cluster,
}
msb, err := json.Marshal(cluster.members)
if err != nil {
t.Fatal(err)
}
wms := string(msb) + "\n"
mb, err := json.Marshal(cluster.members[0])
if err != nil {
t.Fatal(err)
}
wm := string(mb) + "\n"
tests := []struct {
path string
wcode int
wct string
wbody string
}{
{adminMembersPrefix, http.StatusOK, "application/json", wms},
{path.Join(adminMembersPrefix, "1"), http.StatusOK, "application/json", wm},
{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)
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)
}
}
}
func TestServeAdminMembersPut(t *testing.T) {
u := mustNewURL(t, adminMembersPrefix)
raftAttr := etcdserver.RaftAttributes{PeerURLs: []string{"http://127.0.0.1:1"}}