func (cs *ClusterServer) MemberAdd(ctx context.Context, r *pb.MemberAddRequest) (*pb.MemberAddResponse, error) { urls, err := types.NewURLs(r.PeerURLs) if err != nil { return nil, rpctypes.ErrGRPCMemberBadURLs } now := time.Now() m := membership.NewMember("", urls, "", &now) if err = cs.server.AddMember(ctx, *m); err != nil { return nil, togRPCError(err) } return &pb.MemberAddResponse{ Header: cs.header(), Member: &pb.Member{ID: uint64(m.ID), PeerURLs: m.PeerURLs}, }, nil }
func (cs *ClusterServer) MemberAdd(ctx context.Context, r *pb.MemberAddRequest) (*pb.MemberAddResponse, error) { urls, err := types.NewURLs(r.PeerURLs) if err != nil { return nil, rpctypes.ErrMemberBadURLs } now := time.Now() m := membership.NewMember("", urls, "", &now) err = cs.server.AddMember(ctx, *m) switch { case err == membership.ErrIDExists: return nil, rpctypes.ErrMemberExist case err == membership.ErrPeerURLexists: return nil, rpctypes.ErrPeerURLExist case err != nil: return nil, grpc.Errorf(codes.Internal, err.Error()) } return &pb.MemberAddResponse{ Header: cs.header(), Member: &pb.Member{ID: uint64(m.ID), PeerURLs: m.PeerURLs}, }, nil }
func TestGetOtherPeerURLs(t *testing.T) { tests := []struct { membs []*membership.Member wurls []string }{ { []*membership.Member{ membership.NewMember("1", types.MustNewURLs([]string{"http://10.0.0.1:1"}), "a", nil), }, []string{}, }, { []*membership.Member{ membership.NewMember("1", types.MustNewURLs([]string{"http://10.0.0.1:1"}), "a", nil), membership.NewMember("2", types.MustNewURLs([]string{"http://10.0.0.2:2"}), "a", nil), membership.NewMember("3", types.MustNewURLs([]string{"http://10.0.0.3:3"}), "a", nil), }, []string{"http://10.0.0.2:2", "http://10.0.0.3:3"}, }, { []*membership.Member{ membership.NewMember("1", types.MustNewURLs([]string{"http://10.0.0.1:1"}), "a", nil), membership.NewMember("3", types.MustNewURLs([]string{"http://10.0.0.3:3"}), "a", nil), membership.NewMember("2", types.MustNewURLs([]string{"http://10.0.0.2:2"}), "a", nil), }, []string{"http://10.0.0.2:2", "http://10.0.0.3:3"}, }, } for i, tt := range tests { cl := membership.NewClusterFromMembers("", types.ID(0), tt.membs) self := "1" urls := getRemotePeerURLs(cl, self) if !reflect.DeepEqual(urls, tt.wurls) { t.Errorf("#%d: urls = %+v, want %+v", i, urls, tt.wurls) } } }
func (h *membersHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { if !allowMethod(w, r.Method, "GET", "POST", "DELETE", "PUT") { return } if !hasWriteRootAccess(h.sec, r, h.clientCertAuthEnabled) { writeNoAuth(w, r) return } w.Header().Set("X-Etcd-Cluster-ID", h.cluster.ID().String()) ctx, cancel := context.WithTimeout(context.Background(), h.timeout) defer cancel() switch r.Method { case "GET": switch trimPrefix(r.URL.Path, membersPrefix) { case "": mc := newMemberCollection(h.cluster.Members()) w.Header().Set("Content-Type", "application/json") if err := json.NewEncoder(w).Encode(mc); err != nil { plog.Warningf("failed to encode members response (%v)", err) } case "leader": id := h.server.Leader() if id == 0 { writeError(w, r, httptypes.NewHTTPError(http.StatusServiceUnavailable, "During election")) return } m := newMember(h.cluster.Member(id)) w.Header().Set("Content-Type", "application/json") if err := json.NewEncoder(w).Encode(m); err != nil { plog.Warningf("failed to encode members response (%v)", err) } default: writeError(w, r, httptypes.NewHTTPError(http.StatusNotFound, "Not found")) } case "POST": req := httptypes.MemberCreateRequest{} if ok := unmarshalRequest(r, &req, w); !ok { return } now := h.clock.Now() m := membership.NewMember("", req.PeerURLs, "", &now) err := h.server.AddMember(ctx, *m) switch { case err == membership.ErrIDExists || err == membership.ErrPeerURLexists: writeError(w, r, httptypes.NewHTTPError(http.StatusConflict, err.Error())) return case err != nil: plog.Errorf("error adding member %s (%v)", m.ID, err) writeError(w, r, err) return } res := newMember(m) w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusCreated) if err := json.NewEncoder(w).Encode(res); err != nil { plog.Warningf("failed to encode members response (%v)", err) } case "DELETE": id, ok := getID(r.URL.Path, w) if !ok { return } err := h.server.RemoveMember(ctx, uint64(id)) switch { case err == membership.ErrIDRemoved: writeError(w, r, httptypes.NewHTTPError(http.StatusGone, fmt.Sprintf("Member permanently removed: %s", id))) case err == membership.ErrIDNotFound: writeError(w, r, httptypes.NewHTTPError(http.StatusNotFound, fmt.Sprintf("No such member: %s", id))) case err != nil: plog.Errorf("error removing member %s (%v)", id, err) writeError(w, r, err) default: 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 := membership.Member{ ID: id, RaftAttributes: membership.RaftAttributes{PeerURLs: req.PeerURLs.StringSlice()}, } err := h.server.UpdateMember(ctx, m) switch { case err == membership.ErrPeerURLexists: writeError(w, r, httptypes.NewHTTPError(http.StatusConflict, err.Error())) case err == membership.ErrIDNotFound: writeError(w, r, httptypes.NewHTTPError(http.StatusNotFound, fmt.Sprintf("No such member: %s", id))) case err != nil: plog.Errorf("error updating member %s (%v)", m.ID, err) writeError(w, r, err) default: w.WriteHeader(http.StatusNoContent) } } }