From 9b679de9dd2a81610537f145f61aec8cf092b747 Mon Sep 17 00:00:00 2001 From: Jonathan Boulle Date: Thu, 23 Oct 2014 22:52:39 -0700 Subject: [PATCH 1/4] etcdserver/etcdhttp: use container for admin/members endpoint --- etcdserver/etcdhttp/http.go | 6 +++++- etcdserver/etcdhttp/http_test.go | 8 +++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/etcdserver/etcdhttp/http.go b/etcdserver/etcdhttp/http.go index 322665d01..d751ffc0f 100644 --- a/etcdserver/etcdhttp/http.go +++ b/etcdserver/etcdhttp/http.go @@ -163,7 +163,11 @@ func (h serverHandler) serveAdminMembers(w http.ResponseWriter, r *http.Request) case "GET": idStr := strings.TrimPrefix(r.URL.Path, adminMembersPrefix) if idStr == "" { - ms := h.clusterInfo.Members() + ms := struct { + Members []*etcdserver.Member + }{ + Members: h.clusterInfo.Members(), + } w.Header().Set("Content-Type", "application/json") if err := json.NewEncoder(w).Encode(ms); err != nil { log.Printf("etcdhttp: %v", err) diff --git a/etcdserver/etcdhttp/http_test.go b/etcdserver/etcdhttp/http_test.go index e6aada492..0c67b8367 100644 --- a/etcdserver/etcdhttp/http_test.go +++ b/etcdserver/etcdhttp/http_test.go @@ -1627,7 +1627,13 @@ func TestServeAdminMembers(t *testing.T) { clusterInfo: cluster, } - msb, err := json.Marshal([]etcdserver.Member{memb1, memb2}) + msb, err := json.Marshal( + struct { + Members []etcdserver.Member + }{ + Members: []etcdserver.Member{memb1, memb2}, + }, + ) if err != nil { t.Fatal(err) } From 7ef468b315b7c89cff93b645617a3da346b715bf Mon Sep 17 00:00:00 2001 From: Jonathan Boulle Date: Fri, 24 Oct 2014 10:45:59 -0700 Subject: [PATCH 2/4] etcdhttp: remove /v2/admin/members/x serving --- etcdserver/etcdhttp/http.go | 29 +++++++---------------------- etcdserver/etcdhttp/http_test.go | 21 +++------------------ 2 files changed, 10 insertions(+), 40 deletions(-) diff --git a/etcdserver/etcdhttp/http.go b/etcdserver/etcdhttp/http.go index d751ffc0f..ec0afbe25 100644 --- a/etcdserver/etcdhttp/http.go +++ b/etcdserver/etcdhttp/http.go @@ -161,34 +161,19 @@ func (h serverHandler) serveAdminMembers(w http.ResponseWriter, r *http.Request) switch r.Method { case "GET": - idStr := strings.TrimPrefix(r.URL.Path, adminMembersPrefix) - if idStr == "" { - ms := struct { - Members []*etcdserver.Member - }{ - Members: h.clusterInfo.Members(), - } - w.Header().Set("Content-Type", "application/json") - if err := json.NewEncoder(w).Encode(ms); err != nil { - log.Printf("etcdhttp: %v", err) - } + if s := strings.TrimPrefix(r.URL.Path, adminMembersPrefix); s != "" { + http.NotFound(w, r) return } - id, err := strconv.ParseUint(idStr, 16, 64) - if err != nil { - http.Error(w, err.Error(), http.StatusBadRequest) - return - } - m := h.clusterInfo.Member(id) - if m == nil { - http.Error(w, "member not found", http.StatusNotFound) - return + ms := struct { + Members []*etcdserver.Member + }{ + Members: h.clusterInfo.Members(), } w.Header().Set("Content-Type", "application/json") - if err := json.NewEncoder(w).Encode(m); err != nil { + if err := json.NewEncoder(w).Encode(ms); err != nil { log.Printf("etcdhttp: %v", err) } - return case "POST": ctype := r.Header.Get("Content-Type") if ctype != "application/json" { diff --git a/etcdserver/etcdhttp/http_test.go b/etcdserver/etcdhttp/http_test.go index 0c67b8367..6150c81bd 100644 --- a/etcdserver/etcdhttp/http_test.go +++ b/etcdserver/etcdhttp/http_test.go @@ -1563,16 +1563,6 @@ 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{ @@ -1638,11 +1628,6 @@ func TestServeAdminMembers(t *testing.T) { t.Fatal(err) } wms := string(msb) + "\n" - mb, err := json.Marshal(memb1) - if err != nil { - t.Fatal(err) - } - wm := string(mb) + "\n" tests := []struct { path string @@ -1651,8 +1636,8 @@ func TestServeAdminMembers(t *testing.T) { 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"}, + {path.Join(adminMembersPrefix, "100"), http.StatusNotFound, "text/plain; charset=utf-8", "404 page not found\n"}, + {path.Join(adminMembersPrefix, "foobar"), http.StatusNotFound, "text/plain; charset=utf-8", "404 page not found\n"}, } for i, tt := range tests { @@ -1670,7 +1655,7 @@ func TestServeAdminMembers(t *testing.T) { 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) + t.Errorf("#%d: body = %q, want %q", i, rw.Body.String(), tt.wbody) } } } From 14852662ef21a2e25d51c7bbea9c4c5b0f5c45c0 Mon Sep 17 00:00:00 2001 From: Jonathan Boulle Date: Fri, 24 Oct 2014 10:50:47 -0700 Subject: [PATCH 3/4] etcdhttp: rename Members -> members in JSON, update doc --- Documentation/0.5/admin_api.md | 71 ++++++++++++---------------------- etcdserver/etcdhttp/http.go | 2 +- 2 files changed, 26 insertions(+), 47 deletions(-) diff --git a/Documentation/0.5/admin_api.md b/Documentation/0.5/admin_api.md index beaafa3b6..0ec26658d 100644 --- a/Documentation/0.5/admin_api.md +++ b/Documentation/0.5/admin_api.md @@ -1,30 +1,7 @@ ## Admin API -### GET /v2/admin/members/:id -Returns an HTTP 200 OK response code and a representation of the requested member; returns a 404 status code and an error message if the id does not exist. -``` - Example Request: GET - http://localhost:2379/v2/admin/members/272e204152 - Response formats: JSON - Example Response: -``` -```json - [ - { - "ID":"272e204152", - "Name":"node1", - "PeerURLs":[ - "http://10.0.0.10:2379" - ], - "ClientURLs":[ - "http://10.0.0.10:2380" - ] - }, - ] -``` - ### GET /v2/admin/members/ -Return an HTTP 200 OK response code and a representation of all the members; +Return an HTTP 200 OK response code and a representation of all members in the etcd cluster: ``` Example Request: GET http://localhost:2379/v2/admin/members/ @@ -32,28 +9,30 @@ Return an HTTP 200 OK response code and a representation of all the members; Example Response: ``` ```json - [ - { - "ID":"272e204152", - "Name":"node1", - "PeerURLs":[ - "http://10.0.0.10:2379" - ], - "ClientURLs":[ - "http://10.0.0.10:2380" - ] - }, - { - "ID":"2225373f43", - "Name":"node2", - "PeerURLs":[ - "http://127.0.0.11:2379" - ], - "ClientURLs":[ - "http://127.0.0.11:2380" - ] - }, - ] + { + "members": [ + { + "ID":"272e204152", + "Name":"node1", + "PeerURLs":[ + "http://10.0.0.10:2379" + ], + "ClientURLs":[ + "http://10.0.0.10:2380" + ] + }, + { + "ID":"2225373f43", + "Name":"node2", + "PeerURLs":[ + "http://127.0.0.11:2379" + ], + "ClientURLs":[ + "http://127.0.0.11:2380" + ] + }, + ] + } ``` ### POST /v2/admin/members/ diff --git a/etcdserver/etcdhttp/http.go b/etcdserver/etcdhttp/http.go index ec0afbe25..ab69dc1c3 100644 --- a/etcdserver/etcdhttp/http.go +++ b/etcdserver/etcdhttp/http.go @@ -166,7 +166,7 @@ func (h serverHandler) serveAdminMembers(w http.ResponseWriter, r *http.Request) return } ms := struct { - Members []*etcdserver.Member + Members []*etcdserver.Member `json:"members"` }{ Members: h.clusterInfo.Members(), } From 543e12074ae0e315ee034fef744e3817726b3bd5 Mon Sep 17 00:00:00 2001 From: Jonathan Boulle Date: Fri, 24 Oct 2014 10:57:56 -0700 Subject: [PATCH 4/4] etcdserver/member: change JSON fields to lowerCamelCase --- Documentation/0.5/admin_api.md | 18 +++++++++--------- etcdserver/cluster_test.go | 18 +++++++++--------- etcdserver/etcdhttp/http_test.go | 2 +- etcdserver/member.go | 8 ++++---- 4 files changed, 23 insertions(+), 23 deletions(-) diff --git a/Documentation/0.5/admin_api.md b/Documentation/0.5/admin_api.md index 0ec26658d..f8e4b582b 100644 --- a/Documentation/0.5/admin_api.md +++ b/Documentation/0.5/admin_api.md @@ -12,22 +12,22 @@ Return an HTTP 200 OK response code and a representation of all members in the e { "members": [ { - "ID":"272e204152", - "Name":"node1", - "PeerURLs":[ + "id":"272e204152", + "name":"node1", + "peerURLs":[ "http://10.0.0.10:2379" ], - "ClientURLs":[ + "clientURLs":[ "http://10.0.0.10:2380" ] }, { - "ID":"2225373f43", - "Name":"node2", - "PeerURLs":[ + "id":"2225373f43", + "name":"node2", + "peerURLs":[ "http://127.0.0.11:2379" ], - "ClientURLs":[ + "clientURLs":[ "http://127.0.0.11:2380" ] }, @@ -52,7 +52,7 @@ If the POST body is malformed an HTTP 400 will be returned. If the member exists [ { "id":"3777296169", - "PeerURLs":[ + "peerURLs":[ "http://10.0.0.10:2379" ], }, diff --git a/etcdserver/cluster_test.go b/etcdserver/cluster_test.go index 2eeace978..e29a671ba 100644 --- a/etcdserver/cluster_test.go +++ b/etcdserver/cluster_test.go @@ -311,19 +311,19 @@ func TestNodeToMemberBad(t *testing.T) { {Key: "/1234/dynamic", Value: stringp("garbage")}, }}, {Key: "/1234", Nodes: []*store.NodeExtern{ - {Key: "/1234/dynamic", Value: stringp(`{"PeerURLs":null}`)}, + {Key: "/1234/dynamic", Value: stringp(`{"peerURLs":null}`)}, }}, {Key: "/1234", Nodes: []*store.NodeExtern{ - {Key: "/1234/dynamic", Value: stringp(`{"PeerURLs":null}`)}, + {Key: "/1234/dynamic", Value: stringp(`{"peerURLs":null}`)}, {Key: "/1234/strange"}, }}, {Key: "/1234", Nodes: []*store.NodeExtern{ - {Key: "/1234/dynamic", Value: stringp(`{"PeerURLs":null}`)}, + {Key: "/1234/dynamic", Value: stringp(`{"peerURLs":null}`)}, {Key: "/1234/static", Value: stringp("garbage")}, }}, {Key: "/1234", Nodes: []*store.NodeExtern{ - {Key: "/1234/dynamic", Value: stringp(`{"PeerURLs":null}`)}, - {Key: "/1234/static", Value: stringp(`{"Name":"node1","ClientURLs":null}`)}, + {Key: "/1234/dynamic", Value: stringp(`{"peerURLs":null}`)}, + {Key: "/1234/static", Value: stringp(`{"name":"node1","clientURLs":null}`)}, {Key: "/1234/strange"}, }}, } @@ -346,7 +346,7 @@ func TestClusterAddMember(t *testing.T) { params: []interface{}{ path.Join(storeMembersPrefix, "1", "raftAttributes"), false, - `{"PeerURLs":null}`, + `{"peerURLs":null}`, false, store.Permanent, }, @@ -356,7 +356,7 @@ func TestClusterAddMember(t *testing.T) { params: []interface{}{ path.Join(storeMembersPrefix, "1", "attributes"), false, - `{"Name":"node1"}`, + `{"name":"node1"}`, false, store.Permanent, }, @@ -449,8 +449,8 @@ func TestClusterRemoveMember(t *testing.T) { func TestNodeToMember(t *testing.T) { n := &store.NodeExtern{Key: "/1234", Nodes: []*store.NodeExtern{ - {Key: "/1234/attributes", Value: stringp(`{"Name":"node1","ClientURLs":null}`)}, - {Key: "/1234/raftAttributes", Value: stringp(`{"PeerURLs":null}`)}, + {Key: "/1234/attributes", Value: stringp(`{"name":"node1","clientURLs":null}`)}, + {Key: "/1234/raftAttributes", Value: stringp(`{"peerURLs":null}`)}, }} wm := &Member{ID: 0x1234, RaftAttributes: RaftAttributes{}, Attributes: Attributes{Name: "node1"}} m, err := nodeToMember(n) diff --git a/etcdserver/etcdhttp/http_test.go b/etcdserver/etcdhttp/http_test.go index 6150c81bd..cbebc12a0 100644 --- a/etcdserver/etcdhttp/http_test.go +++ b/etcdserver/etcdhttp/http_test.go @@ -1619,7 +1619,7 @@ func TestServeAdminMembers(t *testing.T) { msb, err := json.Marshal( struct { - Members []etcdserver.Member + Members []etcdserver.Member `json:"members"` }{ Members: []etcdserver.Member{memb1, memb2}, }, diff --git a/etcdserver/member.go b/etcdserver/member.go index 51e03a65e..d4ec14f4b 100644 --- a/etcdserver/member.go +++ b/etcdserver/member.go @@ -33,17 +33,17 @@ import ( // RaftAttributes represents the raft related attributes of an etcd member. type RaftAttributes struct { // TODO(philips): ensure these are URLs - PeerURLs []string + PeerURLs []string `json:"peerURLs"` } // Attributes represents all the non-raft related attributes of an etcd member. type Attributes struct { - Name string `json:",omitempty"` - ClientURLs []string `json:",omitempty"` + Name string `json:"name,omitempty"` + ClientURLs []string `json:"clientURLs,omitempty"` } type Member struct { - ID uint64 + ID uint64 `json:"id"` RaftAttributes Attributes }