|
|
@@ -111,6 +111,11 @@ func (s *serverRecorder) RemoveMember(_ context.Context, id uint64) error {
|
|
|
return nil
|
|
|
}
|
|
|
|
|
|
+func (s *serverRecorder) UpdateMember(_ context.Context, m etcdserver.Member) error {
|
|
|
+ s.actions = append(s.actions, action{name: "UpdateMember", params: []interface{}{m}})
|
|
|
+ return nil
|
|
|
+}
|
|
|
+
|
|
|
type action struct {
|
|
|
name string
|
|
|
params []interface{}
|
|
|
@@ -136,11 +141,12 @@ type resServer struct {
|
|
|
func (rs *resServer) Do(_ context.Context, _ etcdserverpb.Request) (etcdserver.Response, error) {
|
|
|
return rs.res, nil
|
|
|
}
|
|
|
-func (rs *resServer) Process(_ context.Context, _ raftpb.Message) error { return nil }
|
|
|
-func (rs *resServer) Start() {}
|
|
|
-func (rs *resServer) Stop() {}
|
|
|
-func (rs *resServer) AddMember(_ context.Context, _ etcdserver.Member) error { return nil }
|
|
|
-func (rs *resServer) RemoveMember(_ context.Context, _ uint64) error { return nil }
|
|
|
+func (rs *resServer) Process(_ context.Context, _ raftpb.Message) error { return nil }
|
|
|
+func (rs *resServer) Start() {}
|
|
|
+func (rs *resServer) Stop() {}
|
|
|
+func (rs *resServer) AddMember(_ context.Context, _ etcdserver.Member) error { return nil }
|
|
|
+func (rs *resServer) RemoveMember(_ context.Context, _ uint64) error { return nil }
|
|
|
+func (rs *resServer) UpdateMember(_ context.Context, _ etcdserver.Member) error { return nil }
|
|
|
|
|
|
func boolp(b bool) *bool { return &b }
|
|
|
|
|
|
@@ -698,6 +704,48 @@ func TestServeMembersDelete(t *testing.T) {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+func TestServeMembersUpdate(t *testing.T) {
|
|
|
+ u := mustNewURL(t, path.Join(membersPrefix, "1"))
|
|
|
+ b := []byte(`{"peerURLs":["http://127.0.0.1:1"]}`)
|
|
|
+ req, err := http.NewRequest("PUT", u.String(), bytes.NewReader(b))
|
|
|
+ if err != nil {
|
|
|
+ t.Fatal(err)
|
|
|
+ }
|
|
|
+ req.Header.Set("Content-Type", "application/json")
|
|
|
+ s := &serverRecorder{}
|
|
|
+ h := &membersHandler{
|
|
|
+ server: s,
|
|
|
+ clock: clockwork.NewFakeClock(),
|
|
|
+ clusterInfo: &fakeCluster{id: 1},
|
|
|
+ }
|
|
|
+ rw := httptest.NewRecorder()
|
|
|
+
|
|
|
+ h.ServeHTTP(rw, req)
|
|
|
+
|
|
|
+ wcode := http.StatusNoContent
|
|
|
+ if rw.Code != wcode {
|
|
|
+ t.Errorf("code=%d, want %d", rw.Code, wcode)
|
|
|
+ }
|
|
|
+
|
|
|
+ gcid := rw.Header().Get("X-Etcd-Cluster-ID")
|
|
|
+ wcid := h.clusterInfo.ID().String()
|
|
|
+ if gcid != wcid {
|
|
|
+ t.Errorf("cid = %s, want %s", gcid, wcid)
|
|
|
+ }
|
|
|
+
|
|
|
+ wm := etcdserver.Member{
|
|
|
+ ID: 1,
|
|
|
+ RaftAttributes: etcdserver.RaftAttributes{
|
|
|
+ PeerURLs: []string{"http://127.0.0.1:1"},
|
|
|
+ },
|
|
|
+ }
|
|
|
+
|
|
|
+ wactions := []action{{name: "UpdateMember", params: []interface{}{wm}}}
|
|
|
+ if !reflect.DeepEqual(s.actions, wactions) {
|
|
|
+ t.Errorf("actions = %+v, want %+v", s.actions, wactions)
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
func TestServeMembersFail(t *testing.T) {
|
|
|
tests := []struct {
|
|
|
req *http.Request
|
|
|
@@ -855,6 +903,104 @@ func TestServeMembersFail(t *testing.T) {
|
|
|
},
|
|
|
nil,
|
|
|
|
|
|
+ http.StatusMethodNotAllowed,
|
|
|
+ },
|
|
|
+ {
|
|
|
+ // parse body error
|
|
|
+ &http.Request{
|
|
|
+ URL: mustNewURL(t, path.Join(membersPrefix, "0")),
|
|
|
+ Method: "PUT",
|
|
|
+ Body: ioutil.NopCloser(strings.NewReader("bad json")),
|
|
|
+ Header: map[string][]string{"Content-Type": []string{"application/json"}},
|
|
|
+ },
|
|
|
+ &resServer{},
|
|
|
+
|
|
|
+ http.StatusBadRequest,
|
|
|
+ },
|
|
|
+ {
|
|
|
+ // bad content type
|
|
|
+ &http.Request{
|
|
|
+ URL: mustNewURL(t, path.Join(membersPrefix, "0")),
|
|
|
+ Method: "PUT",
|
|
|
+ Body: ioutil.NopCloser(strings.NewReader(`{"PeerURLs": ["http://127.0.0.1:1"]}`)),
|
|
|
+ Header: map[string][]string{"Content-Type": []string{"application/bad"}},
|
|
|
+ },
|
|
|
+ &errServer{},
|
|
|
+
|
|
|
+ http.StatusUnsupportedMediaType,
|
|
|
+ },
|
|
|
+ {
|
|
|
+ // bad url
|
|
|
+ &http.Request{
|
|
|
+ URL: mustNewURL(t, path.Join(membersPrefix, "0")),
|
|
|
+ Method: "PUT",
|
|
|
+ Body: ioutil.NopCloser(strings.NewReader(`{"PeerURLs": ["http://a"]}`)),
|
|
|
+ Header: map[string][]string{"Content-Type": []string{"application/json"}},
|
|
|
+ },
|
|
|
+ &errServer{},
|
|
|
+
|
|
|
+ http.StatusBadRequest,
|
|
|
+ },
|
|
|
+ {
|
|
|
+ // etcdserver.UpdateMember error
|
|
|
+ &http.Request{
|
|
|
+ URL: mustNewURL(t, path.Join(membersPrefix, "0")),
|
|
|
+ Method: "PUT",
|
|
|
+ Body: ioutil.NopCloser(strings.NewReader(`{"PeerURLs": ["http://127.0.0.1:1"]}`)),
|
|
|
+ Header: map[string][]string{"Content-Type": []string{"application/json"}},
|
|
|
+ },
|
|
|
+ &errServer{
|
|
|
+ errors.New("blah"),
|
|
|
+ },
|
|
|
+
|
|
|
+ http.StatusInternalServerError,
|
|
|
+ },
|
|
|
+ {
|
|
|
+ // etcdserver.UpdateMember error
|
|
|
+ &http.Request{
|
|
|
+ URL: mustNewURL(t, path.Join(membersPrefix, "0")),
|
|
|
+ Method: "PUT",
|
|
|
+ Body: ioutil.NopCloser(strings.NewReader(`{"PeerURLs": ["http://127.0.0.1:1"]}`)),
|
|
|
+ Header: map[string][]string{"Content-Type": []string{"application/json"}},
|
|
|
+ },
|
|
|
+ &errServer{
|
|
|
+ etcdserver.ErrPeerURLexists,
|
|
|
+ },
|
|
|
+
|
|
|
+ http.StatusConflict,
|
|
|
+ },
|
|
|
+ {
|
|
|
+ // etcdserver.UpdateMember error
|
|
|
+ &http.Request{
|
|
|
+ URL: mustNewURL(t, path.Join(membersPrefix, "0")),
|
|
|
+ Method: "PUT",
|
|
|
+ Body: ioutil.NopCloser(strings.NewReader(`{"PeerURLs": ["http://127.0.0.1:1"]}`)),
|
|
|
+ Header: map[string][]string{"Content-Type": []string{"application/json"}},
|
|
|
+ },
|
|
|
+ &errServer{
|
|
|
+ etcdserver.ErrIDNotFound,
|
|
|
+ },
|
|
|
+
|
|
|
+ http.StatusNotFound,
|
|
|
+ },
|
|
|
+ {
|
|
|
+ // etcdserver.UpdateMember error with badly formed ID
|
|
|
+ &http.Request{
|
|
|
+ URL: mustNewURL(t, path.Join(membersPrefix, "bad_id")),
|
|
|
+ Method: "PUT",
|
|
|
+ },
|
|
|
+ nil,
|
|
|
+
|
|
|
+ http.StatusNotFound,
|
|
|
+ },
|
|
|
+ {
|
|
|
+ // etcdserver.UpdateMember with no ID
|
|
|
+ &http.Request{
|
|
|
+ URL: mustNewURL(t, membersPrefix),
|
|
|
+ Method: "PUT",
|
|
|
+ },
|
|
|
+ nil,
|
|
|
+
|
|
|
http.StatusMethodNotAllowed,
|
|
|
},
|
|
|
}
|
|
|
@@ -995,6 +1141,43 @@ func TestServeMachines(t *testing.T) {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+func TestGetID(t *testing.T) {
|
|
|
+ tests := []struct {
|
|
|
+ path string
|
|
|
+
|
|
|
+ wok bool
|
|
|
+ wid types.ID
|
|
|
+ wcode int
|
|
|
+ }{
|
|
|
+ {
|
|
|
+ "123",
|
|
|
+ true, 0x123, http.StatusOK,
|
|
|
+ },
|
|
|
+ {
|
|
|
+ "bad_id",
|
|
|
+ false, 0, http.StatusNotFound,
|
|
|
+ },
|
|
|
+ {
|
|
|
+ "",
|
|
|
+ false, 0, http.StatusMethodNotAllowed,
|
|
|
+ },
|
|
|
+ }
|
|
|
+
|
|
|
+ for i, tt := range tests {
|
|
|
+ w := httptest.NewRecorder()
|
|
|
+ id, ok := getID(tt.path, w)
|
|
|
+ if id != tt.wid {
|
|
|
+ t.Errorf("#%d: id = %d, want %d", i, id, tt.wid)
|
|
|
+ }
|
|
|
+ if ok != tt.wok {
|
|
|
+ t.Errorf("#%d: ok = %t, want %t", i, ok, tt.wok)
|
|
|
+ }
|
|
|
+ if w.Code != tt.wcode {
|
|
|
+ t.Errorf("#%d code = %d, want %d", i, w.Code, tt.wcode)
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
type dummyStats struct {
|
|
|
data []byte
|
|
|
}
|