1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 package etcdhttp
16
17 import (
18 "encoding/json"
19 "fmt"
20 "net/http"
21 "strconv"
22 "strings"
23
24 "go.etcd.io/etcd/client/pkg/v3/types"
25 "go.etcd.io/etcd/server/v3/etcdserver"
26 "go.etcd.io/etcd/server/v3/etcdserver/api"
27 "go.etcd.io/etcd/server/v3/etcdserver/api/membership"
28 "go.etcd.io/etcd/server/v3/etcdserver/api/rafthttp"
29 "go.etcd.io/etcd/server/v3/lease/leasehttp"
30
31 "go.uber.org/zap"
32 )
33
34 const (
35 peerMembersPath = "/members"
36 peerMemberPromotePrefix = "/members/promote/"
37 )
38
39
40 func NewPeerHandler(lg *zap.Logger, s etcdserver.ServerPeerV2) http.Handler {
41 return newPeerHandler(lg, s, s.RaftHandler(), s.LeaseHandler(), s.HashKVHandler(), s.DowngradeEnabledHandler())
42 }
43
44 func newPeerHandler(
45 lg *zap.Logger,
46 s etcdserver.Server,
47 raftHandler http.Handler,
48 leaseHandler http.Handler,
49 hashKVHandler http.Handler,
50 downgradeEnabledHandler http.Handler,
51 ) http.Handler {
52 if lg == nil {
53 lg = zap.NewNop()
54 }
55 peerMembersHandler := newPeerMembersHandler(lg, s.Cluster())
56 peerMemberPromoteHandler := newPeerMemberPromoteHandler(lg, s)
57
58 mux := http.NewServeMux()
59 mux.HandleFunc("/", http.NotFound)
60 mux.Handle(rafthttp.RaftPrefix, raftHandler)
61 mux.Handle(rafthttp.RaftPrefix+"/", raftHandler)
62 mux.Handle(peerMembersPath, peerMembersHandler)
63 mux.Handle(peerMemberPromotePrefix, peerMemberPromoteHandler)
64 if leaseHandler != nil {
65 mux.Handle(leasehttp.LeasePrefix, leaseHandler)
66 mux.Handle(leasehttp.LeaseInternalPrefix, leaseHandler)
67 }
68 if downgradeEnabledHandler != nil {
69 mux.Handle(etcdserver.DowngradeEnabledPath, downgradeEnabledHandler)
70 }
71 if hashKVHandler != nil {
72 mux.Handle(etcdserver.PeerHashKVPath, hashKVHandler)
73 }
74 mux.HandleFunc(versionPath, versionHandler(s.Cluster(), serveVersion))
75 return mux
76 }
77
78 func newPeerMembersHandler(lg *zap.Logger, cluster api.Cluster) http.Handler {
79 return &peerMembersHandler{
80 lg: lg,
81 cluster: cluster,
82 }
83 }
84
85 type peerMembersHandler struct {
86 lg *zap.Logger
87 cluster api.Cluster
88 }
89
90 func newPeerMemberPromoteHandler(lg *zap.Logger, s etcdserver.Server) http.Handler {
91 return &peerMemberPromoteHandler{
92 lg: lg,
93 cluster: s.Cluster(),
94 server: s,
95 }
96 }
97
98 type peerMemberPromoteHandler struct {
99 lg *zap.Logger
100 cluster api.Cluster
101 server etcdserver.Server
102 }
103
104 func (h *peerMembersHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
105 if !allowMethod(w, r, "GET") {
106 return
107 }
108 w.Header().Set("X-Etcd-Cluster-ID", h.cluster.ID().String())
109
110 if r.URL.Path != peerMembersPath {
111 http.Error(w, "bad path", http.StatusBadRequest)
112 return
113 }
114 ms := h.cluster.Members()
115 w.Header().Set("Content-Type", "application/json")
116 if err := json.NewEncoder(w).Encode(ms); err != nil {
117 h.lg.Warn("failed to encode membership members", zap.Error(err))
118 }
119 }
120
121 func (h *peerMemberPromoteHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
122 if !allowMethod(w, r, "POST") {
123 return
124 }
125 w.Header().Set("X-Etcd-Cluster-ID", h.cluster.ID().String())
126
127 if !strings.HasPrefix(r.URL.Path, peerMemberPromotePrefix) {
128 http.Error(w, "bad path", http.StatusBadRequest)
129 return
130 }
131 idStr := strings.TrimPrefix(r.URL.Path, peerMemberPromotePrefix)
132 id, err := strconv.ParseUint(idStr, 10, 64)
133 if err != nil {
134 http.Error(w, fmt.Sprintf("member %s not found in cluster", idStr), http.StatusNotFound)
135 return
136 }
137
138 resp, err := h.server.PromoteMember(r.Context(), id)
139 if err != nil {
140 switch err {
141 case membership.ErrIDNotFound:
142 http.Error(w, err.Error(), http.StatusNotFound)
143 case membership.ErrMemberNotLearner:
144 http.Error(w, err.Error(), http.StatusPreconditionFailed)
145 case etcdserver.ErrLearnerNotReady:
146 http.Error(w, err.Error(), http.StatusPreconditionFailed)
147 default:
148 WriteError(h.lg, w, r, err)
149 }
150 h.lg.Warn(
151 "failed to promote a member",
152 zap.String("member-id", types.ID(id).String()),
153 zap.Error(err),
154 )
155 return
156 }
157
158 w.Header().Set("Content-Type", "application/json")
159 w.WriteHeader(http.StatusOK)
160 if err := json.NewEncoder(w).Encode(resp); err != nil {
161 h.lg.Warn("failed to encode members response", zap.Error(err))
162 }
163 }
164
View as plain text