1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 package client
16
17 import (
18 "bytes"
19 "context"
20 "encoding/json"
21 "fmt"
22 "net/http"
23 "net/url"
24 "path"
25
26 "go.etcd.io/etcd/client/pkg/v3/types"
27 )
28
29 var (
30 defaultV2MembersPrefix = "/v2/members"
31 defaultLeaderSuffix = "/leader"
32 )
33
34 type Member struct {
35
36 ID string `json:"id"`
37
38
39 Name string `json:"name"`
40
41
42
43 PeerURLs []string `json:"peerURLs"`
44
45
46
47 ClientURLs []string `json:"clientURLs"`
48 }
49
50 type memberCollection []Member
51
52 func (c *memberCollection) UnmarshalJSON(data []byte) error {
53 d := struct {
54 Members []Member
55 }{}
56
57 if err := json.Unmarshal(data, &d); err != nil {
58 return err
59 }
60
61 if d.Members == nil {
62 *c = make([]Member, 0)
63 return nil
64 }
65
66 *c = d.Members
67 return nil
68 }
69
70 type memberCreateOrUpdateRequest struct {
71 PeerURLs types.URLs
72 }
73
74 func (m *memberCreateOrUpdateRequest) MarshalJSON() ([]byte, error) {
75 s := struct {
76 PeerURLs []string `json:"peerURLs"`
77 }{
78 PeerURLs: make([]string, len(m.PeerURLs)),
79 }
80
81 for i, u := range m.PeerURLs {
82 s.PeerURLs[i] = u.String()
83 }
84
85 return json.Marshal(&s)
86 }
87
88
89
90 func NewMembersAPI(c Client) MembersAPI {
91 return &httpMembersAPI{
92 client: c,
93 }
94 }
95
96 type MembersAPI interface {
97
98 List(ctx context.Context) ([]Member, error)
99
100
101 Add(ctx context.Context, peerURL string) (*Member, error)
102
103
104 Remove(ctx context.Context, mID string) error
105
106
107 Update(ctx context.Context, mID string, peerURLs []string) error
108
109
110 Leader(ctx context.Context) (*Member, error)
111 }
112
113 type httpMembersAPI struct {
114 client httpClient
115 }
116
117 func (m *httpMembersAPI) List(ctx context.Context) ([]Member, error) {
118 req := &membersAPIActionList{}
119 resp, body, err := m.client.Do(ctx, req)
120 if err != nil {
121 return nil, err
122 }
123
124 if err := assertStatusCode(resp.StatusCode, http.StatusOK); err != nil {
125 return nil, err
126 }
127
128 var mCollection memberCollection
129 if err := json.Unmarshal(body, &mCollection); err != nil {
130 return nil, err
131 }
132
133 return []Member(mCollection), nil
134 }
135
136 func (m *httpMembersAPI) Add(ctx context.Context, peerURL string) (*Member, error) {
137 urls, err := types.NewURLs([]string{peerURL})
138 if err != nil {
139 return nil, err
140 }
141
142 req := &membersAPIActionAdd{peerURLs: urls}
143 resp, body, err := m.client.Do(ctx, req)
144 if err != nil {
145 return nil, err
146 }
147
148 if err := assertStatusCode(resp.StatusCode, http.StatusCreated, http.StatusConflict); err != nil {
149 return nil, err
150 }
151
152 if resp.StatusCode != http.StatusCreated {
153 var merr membersError
154 if err := json.Unmarshal(body, &merr); err != nil {
155 return nil, err
156 }
157 return nil, merr
158 }
159
160 var memb Member
161 if err := json.Unmarshal(body, &memb); err != nil {
162 return nil, err
163 }
164
165 return &memb, nil
166 }
167
168 func (m *httpMembersAPI) Update(ctx context.Context, memberID string, peerURLs []string) error {
169 urls, err := types.NewURLs(peerURLs)
170 if err != nil {
171 return err
172 }
173
174 req := &membersAPIActionUpdate{peerURLs: urls, memberID: memberID}
175 resp, body, err := m.client.Do(ctx, req)
176 if err != nil {
177 return err
178 }
179
180 if err := assertStatusCode(resp.StatusCode, http.StatusNoContent, http.StatusNotFound, http.StatusConflict); err != nil {
181 return err
182 }
183
184 if resp.StatusCode != http.StatusNoContent {
185 var merr membersError
186 if err := json.Unmarshal(body, &merr); err != nil {
187 return err
188 }
189 return merr
190 }
191
192 return nil
193 }
194
195 func (m *httpMembersAPI) Remove(ctx context.Context, memberID string) error {
196 req := &membersAPIActionRemove{memberID: memberID}
197 resp, _, err := m.client.Do(ctx, req)
198 if err != nil {
199 return err
200 }
201
202 return assertStatusCode(resp.StatusCode, http.StatusNoContent, http.StatusGone)
203 }
204
205 func (m *httpMembersAPI) Leader(ctx context.Context) (*Member, error) {
206 req := &membersAPIActionLeader{}
207 resp, body, err := m.client.Do(ctx, req)
208 if err != nil {
209 return nil, err
210 }
211
212 if err := assertStatusCode(resp.StatusCode, http.StatusOK); err != nil {
213 return nil, err
214 }
215
216 var leader Member
217 if err := json.Unmarshal(body, &leader); err != nil {
218 return nil, err
219 }
220
221 return &leader, nil
222 }
223
224 type membersAPIActionList struct{}
225
226 func (l *membersAPIActionList) HTTPRequest(ep url.URL) *http.Request {
227 u := v2MembersURL(ep)
228 req, _ := http.NewRequest("GET", u.String(), nil)
229 return req
230 }
231
232 type membersAPIActionRemove struct {
233 memberID string
234 }
235
236 func (d *membersAPIActionRemove) HTTPRequest(ep url.URL) *http.Request {
237 u := v2MembersURL(ep)
238 u.Path = path.Join(u.Path, d.memberID)
239 req, _ := http.NewRequest("DELETE", u.String(), nil)
240 return req
241 }
242
243 type membersAPIActionAdd struct {
244 peerURLs types.URLs
245 }
246
247 func (a *membersAPIActionAdd) HTTPRequest(ep url.URL) *http.Request {
248 u := v2MembersURL(ep)
249 m := memberCreateOrUpdateRequest{PeerURLs: a.peerURLs}
250 b, _ := json.Marshal(&m)
251 req, _ := http.NewRequest("POST", u.String(), bytes.NewReader(b))
252 req.Header.Set("Content-Type", "application/json")
253 return req
254 }
255
256 type membersAPIActionUpdate struct {
257 memberID string
258 peerURLs types.URLs
259 }
260
261 func (a *membersAPIActionUpdate) HTTPRequest(ep url.URL) *http.Request {
262 u := v2MembersURL(ep)
263 m := memberCreateOrUpdateRequest{PeerURLs: a.peerURLs}
264 u.Path = path.Join(u.Path, a.memberID)
265 b, _ := json.Marshal(&m)
266 req, _ := http.NewRequest("PUT", u.String(), bytes.NewReader(b))
267 req.Header.Set("Content-Type", "application/json")
268 return req
269 }
270
271 func assertStatusCode(got int, want ...int) (err error) {
272 for _, w := range want {
273 if w == got {
274 return nil
275 }
276 }
277 return fmt.Errorf("unexpected status code %d", got)
278 }
279
280 type membersAPIActionLeader struct{}
281
282 func (l *membersAPIActionLeader) HTTPRequest(ep url.URL) *http.Request {
283 u := v2MembersURL(ep)
284 u.Path = path.Join(u.Path, defaultLeaderSuffix)
285 req, _ := http.NewRequest("GET", u.String(), nil)
286 return req
287 }
288
289
290
291 func v2MembersURL(ep url.URL) *url.URL {
292 ep.Path = path.Join(ep.Path, defaultV2MembersPrefix)
293 return &ep
294 }
295
296 type membersError struct {
297 Message string `json:"message"`
298 Code int `json:"-"`
299 }
300
301 func (e membersError) Error() string {
302 return e.Message
303 }
304
View as plain text