Browse Source

Merge pull request #1365 from xiangli-cmu/admin_get

etcdserver: support GET on admin endpoint
Xiang Li 11 years ago
parent
commit
16c9970a03
2 changed files with 103 additions and 1 deletions
  1. 38 1
      etcdserver/etcdhttp/http.go
  2. 65 0
      etcdserver/etcdhttp/http_test.go

+ 38 - 1
etcdserver/etcdhttp/http.go

@@ -25,6 +25,7 @@ import (
 	"net/http"
 	"net/http"
 	"net/url"
 	"net/url"
 	"path"
 	"path"
+	"sort"
 	"strconv"
 	"strconv"
 	"strings"
 	"strings"
 	"time"
 	"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) {
 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
 		return
 	}
 	}
 	ctx, cancel := context.WithTimeout(context.Background(), defaultServerTimeout)
 	ctx, cancel := context.WithTimeout(context.Background(), defaultServerTimeout)
 	defer cancel()
 	defer cancel()
 
 
 	switch r.Method {
 	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":
 	case "POST":
 		ctype := r.Header.Get("Content-Type")
 		ctype := r.Header.Get("Content-Type")
 		if ctype != "application/json" {
 		if ctype != "application/json" {
@@ -551,3 +582,9 @@ func trimNodeExternPrefix(n *store.NodeExtern, prefix string) *store.NodeExtern
 	}
 	}
 	return n
 	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] }

+ 65 - 0
etcdserver/etcdhttp/http_test.go

@@ -1485,6 +1485,16 @@ func TestServeAdminMembersFail(t *testing.T) {
 
 
 			http.StatusInternalServerError,
 			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 {
 	for i, tt := range tests {
 		h := &serverHandler{
 		h := &serverHandler{
@@ -1527,6 +1537,61 @@ func (s *serverRecorder) RemoveMember(_ context.Context, id uint64) error {
 	return nil
 	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) {
 func TestServeAdminMembersPut(t *testing.T) {
 	u := mustNewURL(t, adminMembersPrefix)
 	u := mustNewURL(t, adminMembersPrefix)
 	raftAttr := etcdserver.RaftAttributes{PeerURLs: []string{"http://127.0.0.1:1"}}
 	raftAttr := etcdserver.RaftAttributes{PeerURLs: []string{"http://127.0.0.1:1"}}