Merge pull request #1465 from bcwaldon/member-post
Clean up POST /v2/admin/members
This commit is contained in:
@ -36,7 +36,6 @@ import (
|
||||
"github.com/coreos/etcd/etcdserver/etcdhttp/httptypes"
|
||||
"github.com/coreos/etcd/etcdserver/etcdserverpb"
|
||||
"github.com/coreos/etcd/pkg/strutil"
|
||||
"github.com/coreos/etcd/pkg/types"
|
||||
"github.com/coreos/etcd/store"
|
||||
"github.com/coreos/etcd/version"
|
||||
)
|
||||
@ -173,7 +172,7 @@ func (h *adminMembersHandler) ServeHTTP(w http.ResponseWriter, r *http.Request)
|
||||
case "POST":
|
||||
ctype := r.Header.Get("Content-Type")
|
||||
if ctype != "application/json" {
|
||||
writeError(w, httptypes.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Bad Content-Type %s, accept application/json", ctype)))
|
||||
writeError(w, httptypes.NewHTTPError(http.StatusUnsupportedMediaType, fmt.Sprintf("Bad Content-Type %s, accept application/json", ctype)))
|
||||
return
|
||||
}
|
||||
b, err := ioutil.ReadAll(r.Body)
|
||||
@ -181,27 +180,25 @@ func (h *adminMembersHandler) ServeHTTP(w http.ResponseWriter, r *http.Request)
|
||||
writeError(w, httptypes.NewHTTPError(http.StatusBadRequest, err.Error()))
|
||||
return
|
||||
}
|
||||
raftAttr := etcdserver.RaftAttributes{}
|
||||
if err := json.Unmarshal(b, &raftAttr); err != nil {
|
||||
req := httptypes.MemberCreateRequest{}
|
||||
if err := json.Unmarshal(b, &req); err != nil {
|
||||
writeError(w, httptypes.NewHTTPError(http.StatusBadRequest, err.Error()))
|
||||
return
|
||||
}
|
||||
validURLs, err := types.NewURLs(raftAttr.PeerURLs)
|
||||
if err != nil {
|
||||
writeError(w, httptypes.NewHTTPError(http.StatusBadRequest, "Bad peer urls"))
|
||||
return
|
||||
}
|
||||
|
||||
now := h.clock.Now()
|
||||
m := etcdserver.NewMember("", validURLs, "", &now)
|
||||
m := etcdserver.NewMember("", req.PeerURLs, "", &now)
|
||||
if err := h.server.AddMember(ctx, *m); err != nil {
|
||||
log.Printf("etcdhttp: error adding node %x: %v", m.ID, err)
|
||||
writeError(w, err)
|
||||
return
|
||||
}
|
||||
log.Printf("etcdhttp: added node %x with peer urls %v", m.ID, raftAttr.PeerURLs)
|
||||
log.Printf("etcdhttp: added node %x with peer urls %v", m.ID, req.PeerURLs)
|
||||
|
||||
res := newMember(m)
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(http.StatusCreated)
|
||||
if err := json.NewEncoder(w).Encode(m); err != nil {
|
||||
if err := json.NewEncoder(w).Encode(res); err != nil {
|
||||
log.Printf("etcdhttp: %v", err)
|
||||
}
|
||||
case "DELETE":
|
||||
@ -534,18 +531,22 @@ func newMemberCollection(ms []*etcdserver.Member) httptypes.MemberCollection {
|
||||
c := httptypes.MemberCollection(make([]httptypes.Member, len(ms)))
|
||||
|
||||
for i, m := range ms {
|
||||
tm := httptypes.Member{
|
||||
ID: strutil.IDAsHex(m.ID),
|
||||
Name: m.Name,
|
||||
PeerURLs: make([]string, len(m.PeerURLs)),
|
||||
ClientURLs: make([]string, len(m.ClientURLs)),
|
||||
}
|
||||
|
||||
copy(tm.PeerURLs, m.PeerURLs)
|
||||
copy(tm.ClientURLs, m.ClientURLs)
|
||||
|
||||
c[i] = tm
|
||||
c[i] = newMember(m)
|
||||
}
|
||||
|
||||
return c
|
||||
}
|
||||
|
||||
func newMember(m *etcdserver.Member) httptypes.Member {
|
||||
tm := httptypes.Member{
|
||||
ID: strutil.IDAsHex(m.ID),
|
||||
Name: m.Name,
|
||||
PeerURLs: make([]string, len(m.PeerURLs)),
|
||||
ClientURLs: make([]string, len(m.ClientURLs)),
|
||||
}
|
||||
|
||||
copy(tm.PeerURLs, m.PeerURLs)
|
||||
copy(tm.ClientURLs, m.ClientURLs)
|
||||
|
||||
return tm
|
||||
}
|
||||
|
@ -601,15 +601,10 @@ func TestServeAdminMembers(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestServeAdminMembersPut(t *testing.T) {
|
||||
func TestServeAdminMembersCreate(t *testing.T) {
|
||||
u := mustNewURL(t, adminMembersPrefix)
|
||||
raftAttr := etcdserver.RaftAttributes{PeerURLs: []string{"http://127.0.0.1:1"}}
|
||||
b, err := json.Marshal(raftAttr)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
body := bytes.NewReader(b)
|
||||
req, err := http.NewRequest("POST", u.String(), body)
|
||||
b := []byte(`{"peerURLs":["http://127.0.0.1:1"]}`)
|
||||
req, err := http.NewRequest("POST", u.String(), bytes.NewReader(b))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@ -628,15 +623,7 @@ func TestServeAdminMembersPut(t *testing.T) {
|
||||
if rw.Code != wcode {
|
||||
t.Errorf("code=%d, want %d", rw.Code, wcode)
|
||||
}
|
||||
wm := etcdserver.Member{
|
||||
ID: 3064321551348478165,
|
||||
RaftAttributes: raftAttr,
|
||||
}
|
||||
|
||||
wb, err := json.Marshal(wm)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
wct := "application/json"
|
||||
if gct := rw.Header().Get("Content-Type"); gct != wct {
|
||||
t.Errorf("content-type = %s, want %s", gct, wct)
|
||||
@ -646,11 +633,20 @@ func TestServeAdminMembersPut(t *testing.T) {
|
||||
if gcid != wcid {
|
||||
t.Errorf("cid = %s, want %s", gcid, wcid)
|
||||
}
|
||||
|
||||
wb := `{"id":"2a86a83729b330d5","name":"","peerURLs":["http://127.0.0.1:1"],"clientURLs":[]}` + "\n"
|
||||
g := rw.Body.String()
|
||||
w := string(wb) + "\n"
|
||||
if g != w {
|
||||
t.Errorf("got body=%q, want %q", g, w)
|
||||
if g != wb {
|
||||
t.Errorf("got body=%q, want %q", g, wb)
|
||||
}
|
||||
|
||||
wm := etcdserver.Member{
|
||||
ID: 3064321551348478165,
|
||||
RaftAttributes: etcdserver.RaftAttributes{
|
||||
PeerURLs: []string{"http://127.0.0.1:1"},
|
||||
},
|
||||
}
|
||||
|
||||
wactions := []action{{name: "AddMember", params: []interface{}{wm}}}
|
||||
if !reflect.DeepEqual(s.actions, wactions) {
|
||||
t.Errorf("actions = %+v, want %+v", s.actions, wactions)
|
||||
@ -721,6 +717,7 @@ func TestServeAdminMembersFail(t *testing.T) {
|
||||
URL: mustNewURL(t, adminMembersPrefix),
|
||||
Method: "POST",
|
||||
Body: ioutil.NopCloser(strings.NewReader("bad json")),
|
||||
Header: map[string][]string{"Content-Type": []string{"application/json"}},
|
||||
},
|
||||
&resServer{},
|
||||
|
||||
@ -736,7 +733,7 @@ func TestServeAdminMembersFail(t *testing.T) {
|
||||
},
|
||||
&errServer{},
|
||||
|
||||
http.StatusBadRequest,
|
||||
http.StatusUnsupportedMediaType,
|
||||
},
|
||||
{
|
||||
// bad url
|
||||
@ -1598,3 +1595,22 @@ func TestNewMemberCollection(t *testing.T) {
|
||||
t.Fatalf("newMemberCollection failure: want=%#v, got=%#v", want, got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewMember(t *testing.T) {
|
||||
fixture := &etcdserver.Member{
|
||||
ID: 12,
|
||||
Attributes: etcdserver.Attributes{ClientURLs: []string{"http://localhost:8080", "http://localhost:8081"}},
|
||||
RaftAttributes: etcdserver.RaftAttributes{PeerURLs: []string{"http://localhost:8082", "http://localhost:8083"}},
|
||||
}
|
||||
got := newMember(fixture)
|
||||
|
||||
want := httptypes.Member{
|
||||
ID: "c",
|
||||
ClientURLs: []string{"http://localhost:8080", "http://localhost:8081"},
|
||||
PeerURLs: []string{"http://localhost:8082", "http://localhost:8083"},
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(want, got) {
|
||||
t.Fatalf("newMember failure: want=%#v, got=%#v", want, got)
|
||||
}
|
||||
}
|
||||
|
@ -18,6 +18,8 @@ package httptypes
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/coreos/etcd/pkg/types"
|
||||
)
|
||||
|
||||
type Member struct {
|
||||
@ -27,6 +29,29 @@ type Member struct {
|
||||
ClientURLs []string `json:"clientURLs"`
|
||||
}
|
||||
|
||||
type MemberCreateRequest struct {
|
||||
PeerURLs types.URLs
|
||||
}
|
||||
|
||||
func (m *MemberCreateRequest) UnmarshalJSON(data []byte) error {
|
||||
s := struct {
|
||||
PeerURLs []string `json:"peerURLs"`
|
||||
}{}
|
||||
|
||||
err := json.Unmarshal(data, &s)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
urls, err := types.NewURLs(s.PeerURLs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
m.PeerURLs = urls
|
||||
return nil
|
||||
}
|
||||
|
||||
type MemberCollection []Member
|
||||
|
||||
func (c *MemberCollection) MarshalJSON() ([]byte, error) {
|
||||
|
@ -18,8 +18,11 @@ package httptypes
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/url"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/coreos/etcd/pkg/types"
|
||||
)
|
||||
|
||||
func TestMemberUnmarshal(t *testing.T) {
|
||||
@ -155,3 +158,44 @@ func TestMemberCollectionUnmarshal(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestMemberCreateRequestUnmarshal(t *testing.T) {
|
||||
body := []byte(`{"peerURLs": ["http://127.0.0.1:8081", "https://127.0.0.1:8080"]}`)
|
||||
want := MemberCreateRequest{
|
||||
PeerURLs: types.URLs([]url.URL{
|
||||
url.URL{Scheme: "http", Host: "127.0.0.1:8081"},
|
||||
url.URL{Scheme: "https", Host: "127.0.0.1:8080"},
|
||||
}),
|
||||
}
|
||||
|
||||
var req MemberCreateRequest
|
||||
if err := json.Unmarshal(body, &req); err != nil {
|
||||
t.Fatalf("Unmarshal returned unexpected err=%v", err)
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(want, req) {
|
||||
t.Fatalf("Failed to unmarshal MemberCreateRequest: want=%#v, got=%#v", want, req)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMemberCreateRequestUnmarshalFail(t *testing.T) {
|
||||
tests := [][]byte{
|
||||
// invalid JSON
|
||||
[]byte(``),
|
||||
[]byte(`{`),
|
||||
|
||||
// spot-check validation done in types.NewURLs
|
||||
[]byte(`{"peerURLs": "foo"}`),
|
||||
[]byte(`{"peerURLs": ["."]}`),
|
||||
[]byte(`{"peerURLs": []}`),
|
||||
[]byte(`{"peerURLs": ["http://127.0.0.1:4001/foo"]}`),
|
||||
[]byte(`{"peerURLs": ["http://127.0.0.1"]}`),
|
||||
}
|
||||
|
||||
for i, tt := range tests {
|
||||
var req MemberCreateRequest
|
||||
if err := json.Unmarshal(tt, &req); err == nil {
|
||||
t.Errorf("#%d: expected err, got nil", i)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user