|
@@ -148,7 +148,7 @@ type membersHandler struct {
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
func (h *membersHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|
func (h *membersHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|
|
- if !allowMethod(w, r.Method, "GET", "POST", "DELETE") {
|
|
|
|
|
|
|
+ if !allowMethod(w, r.Method, "GET", "POST", "DELETE", "PUT") {
|
|
|
return
|
|
return
|
|
|
}
|
|
}
|
|
|
w.Header().Set("X-Etcd-Cluster-ID", h.clusterInfo.ID().String())
|
|
w.Header().Set("X-Etcd-Cluster-ID", h.clusterInfo.ID().String())
|
|
@@ -168,25 +168,13 @@ func (h *membersHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|
|
log.Printf("etcdhttp: %v", err)
|
|
log.Printf("etcdhttp: %v", err)
|
|
|
}
|
|
}
|
|
|
case "POST":
|
|
case "POST":
|
|
|
- ctype := r.Header.Get("Content-Type")
|
|
|
|
|
- if ctype != "application/json" {
|
|
|
|
|
- writeError(w, httptypes.NewHTTPError(http.StatusUnsupportedMediaType, fmt.Sprintf("Bad Content-Type %s, accept application/json", ctype)))
|
|
|
|
|
- return
|
|
|
|
|
- }
|
|
|
|
|
- b, err := ioutil.ReadAll(r.Body)
|
|
|
|
|
- if err != nil {
|
|
|
|
|
- writeError(w, httptypes.NewHTTPError(http.StatusBadRequest, err.Error()))
|
|
|
|
|
- return
|
|
|
|
|
- }
|
|
|
|
|
req := httptypes.MemberCreateRequest{}
|
|
req := httptypes.MemberCreateRequest{}
|
|
|
- if err := json.Unmarshal(b, &req); err != nil {
|
|
|
|
|
- writeError(w, httptypes.NewHTTPError(http.StatusBadRequest, err.Error()))
|
|
|
|
|
|
|
+ if ok := unmarshalRequest(r, &req, w); !ok {
|
|
|
return
|
|
return
|
|
|
}
|
|
}
|
|
|
-
|
|
|
|
|
now := h.clock.Now()
|
|
now := h.clock.Now()
|
|
|
m := etcdserver.NewMember("", req.PeerURLs, "", &now)
|
|
m := etcdserver.NewMember("", req.PeerURLs, "", &now)
|
|
|
- err = h.server.AddMember(ctx, *m)
|
|
|
|
|
|
|
+ err := h.server.AddMember(ctx, *m)
|
|
|
switch {
|
|
switch {
|
|
|
case err == etcdserver.ErrIDExists || err == etcdserver.ErrPeerURLexists:
|
|
case err == etcdserver.ErrIDExists || err == etcdserver.ErrPeerURLexists:
|
|
|
writeError(w, httptypes.NewHTTPError(http.StatusConflict, err.Error()))
|
|
writeError(w, httptypes.NewHTTPError(http.StatusConflict, err.Error()))
|
|
@@ -203,28 +191,47 @@ func (h *membersHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|
|
log.Printf("etcdhttp: %v", err)
|
|
log.Printf("etcdhttp: %v", err)
|
|
|
}
|
|
}
|
|
|
case "DELETE":
|
|
case "DELETE":
|
|
|
- idStr := trimPrefix(r.URL.Path, membersPrefix)
|
|
|
|
|
- if idStr == "" {
|
|
|
|
|
- http.Error(w, "Method Not Allowed", http.StatusMethodNotAllowed)
|
|
|
|
|
- return
|
|
|
|
|
- }
|
|
|
|
|
- id, err := types.IDFromString(idStr)
|
|
|
|
|
- if err != nil {
|
|
|
|
|
- writeError(w, httptypes.NewHTTPError(http.StatusNotFound, fmt.Sprintf("No such member: %s", idStr)))
|
|
|
|
|
|
|
+ id, ok := getID(r.URL.Path, w)
|
|
|
|
|
+ if !ok {
|
|
|
return
|
|
return
|
|
|
}
|
|
}
|
|
|
- err = h.server.RemoveMember(ctx, uint64(id))
|
|
|
|
|
|
|
+ err := h.server.RemoveMember(ctx, uint64(id))
|
|
|
switch {
|
|
switch {
|
|
|
case err == etcdserver.ErrIDRemoved:
|
|
case err == etcdserver.ErrIDRemoved:
|
|
|
- writeError(w, httptypes.NewHTTPError(http.StatusGone, fmt.Sprintf("Member permanently removed: %s", idStr)))
|
|
|
|
|
|
|
+ writeError(w, httptypes.NewHTTPError(http.StatusGone, fmt.Sprintf("Member permanently removed: %s", id)))
|
|
|
case err == etcdserver.ErrIDNotFound:
|
|
case err == etcdserver.ErrIDNotFound:
|
|
|
- writeError(w, httptypes.NewHTTPError(http.StatusNotFound, fmt.Sprintf("No such member: %s", idStr)))
|
|
|
|
|
|
|
+ writeError(w, httptypes.NewHTTPError(http.StatusNotFound, fmt.Sprintf("No such member: %s", id)))
|
|
|
case err != nil:
|
|
case err != nil:
|
|
|
log.Printf("etcdhttp: error removing node %s: %v", id, err)
|
|
log.Printf("etcdhttp: error removing node %s: %v", id, err)
|
|
|
writeError(w, err)
|
|
writeError(w, err)
|
|
|
default:
|
|
default:
|
|
|
w.WriteHeader(http.StatusNoContent)
|
|
w.WriteHeader(http.StatusNoContent)
|
|
|
}
|
|
}
|
|
|
|
|
+ case "PUT":
|
|
|
|
|
+ id, ok := getID(r.URL.Path, w)
|
|
|
|
|
+ if !ok {
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+ req := httptypes.MemberUpdateRequest{}
|
|
|
|
|
+ if ok := unmarshalRequest(r, &req, w); !ok {
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+ m := etcdserver.Member{
|
|
|
|
|
+ ID: id,
|
|
|
|
|
+ RaftAttributes: etcdserver.RaftAttributes{PeerURLs: req.PeerURLs.StringSlice()},
|
|
|
|
|
+ }
|
|
|
|
|
+ err := h.server.UpdateMember(ctx, m)
|
|
|
|
|
+ switch {
|
|
|
|
|
+ case err == etcdserver.ErrPeerURLexists:
|
|
|
|
|
+ writeError(w, httptypes.NewHTTPError(http.StatusConflict, err.Error()))
|
|
|
|
|
+ case err == etcdserver.ErrIDNotFound:
|
|
|
|
|
+ writeError(w, httptypes.NewHTTPError(http.StatusNotFound, fmt.Sprintf("No such member: %s", id)))
|
|
|
|
|
+ case err != nil:
|
|
|
|
|
+ log.Printf("etcdhttp: error updating node %s: %v", m.ID, err)
|
|
|
|
|
+ writeError(w, err)
|
|
|
|
|
+ default:
|
|
|
|
|
+ w.WriteHeader(http.StatusNoContent)
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -506,6 +513,38 @@ func trimErrorPrefix(err error, prefix string) error {
|
|
|
return err
|
|
return err
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+func unmarshalRequest(r *http.Request, req json.Unmarshaler, w http.ResponseWriter) bool {
|
|
|
|
|
+ ctype := r.Header.Get("Content-Type")
|
|
|
|
|
+ if ctype != "application/json" {
|
|
|
|
|
+ writeError(w, httptypes.NewHTTPError(http.StatusUnsupportedMediaType, fmt.Sprintf("Bad Content-Type %s, accept application/json", ctype)))
|
|
|
|
|
+ return false
|
|
|
|
|
+ }
|
|
|
|
|
+ b, err := ioutil.ReadAll(r.Body)
|
|
|
|
|
+ if err != nil {
|
|
|
|
|
+ writeError(w, httptypes.NewHTTPError(http.StatusBadRequest, err.Error()))
|
|
|
|
|
+ return false
|
|
|
|
|
+ }
|
|
|
|
|
+ if err := req.UnmarshalJSON(b); err != nil {
|
|
|
|
|
+ writeError(w, httptypes.NewHTTPError(http.StatusBadRequest, err.Error()))
|
|
|
|
|
+ return false
|
|
|
|
|
+ }
|
|
|
|
|
+ return true
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+func getID(p string, w http.ResponseWriter) (types.ID, bool) {
|
|
|
|
|
+ idStr := trimPrefix(p, membersPrefix)
|
|
|
|
|
+ if idStr == "" {
|
|
|
|
|
+ http.Error(w, "Method Not Allowed", http.StatusMethodNotAllowed)
|
|
|
|
|
+ return 0, false
|
|
|
|
|
+ }
|
|
|
|
|
+ id, err := types.IDFromString(idStr)
|
|
|
|
|
+ if err != nil {
|
|
|
|
|
+ writeError(w, httptypes.NewHTTPError(http.StatusNotFound, fmt.Sprintf("No such member: %s", idStr)))
|
|
|
|
|
+ return 0, false
|
|
|
|
|
+ }
|
|
|
|
|
+ return id, true
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
// getUint64 extracts a uint64 by the given key from a Form. If the key does
|
|
// getUint64 extracts a uint64 by the given key from a Form. If the key does
|
|
|
// not exist in the form, 0 is returned. If the key exists but the value is
|
|
// not exist in the form, 0 is returned. If the key exists but the value is
|
|
|
// badly formed, an error is returned. If multiple values are present only the
|
|
// badly formed, an error is returned. If multiple values are present only the
|