blob: 657131ab0ce0329f91a0329284b01fc97007c031 [file] [log] [blame]
khenaidoo59ce9dd2019-11-11 13:05:32 -05001// Copyright 2015 The etcd Authors
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15package client
16
17import (
18 "bytes"
19 "context"
20 "encoding/json"
21 "fmt"
22 "net/http"
23 "net/url"
24 "path"
25
26 "go.etcd.io/etcd/pkg/types"
27)
28
29var (
30 defaultV2MembersPrefix = "/v2/members"
31 defaultLeaderSuffix = "/leader"
32)
33
34type Member struct {
35 // ID is the unique identifier of this Member.
36 ID string `json:"id"`
37
38 // Name is a human-readable, non-unique identifier of this Member.
39 Name string `json:"name"`
40
41 // PeerURLs represents the HTTP(S) endpoints this Member uses to
42 // participate in etcd's consensus protocol.
43 PeerURLs []string `json:"peerURLs"`
44
45 // ClientURLs represents the HTTP(S) endpoints on which this Member
46 // serves its client-facing APIs.
47 ClientURLs []string `json:"clientURLs"`
48}
49
50type memberCollection []Member
51
52func (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
70type memberCreateOrUpdateRequest struct {
71 PeerURLs types.URLs
72}
73
74func (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// NewMembersAPI constructs a new MembersAPI that uses HTTP to
89// interact with etcd's membership API.
90func NewMembersAPI(c Client) MembersAPI {
91 return &httpMembersAPI{
92 client: c,
93 }
94}
95
96type MembersAPI interface {
97 // List enumerates the current cluster membership.
98 List(ctx context.Context) ([]Member, error)
99
100 // Add instructs etcd to accept a new Member into the cluster.
101 Add(ctx context.Context, peerURL string) (*Member, error)
102
103 // Remove demotes an existing Member out of the cluster.
104 Remove(ctx context.Context, mID string) error
105
106 // Update instructs etcd to update an existing Member in the cluster.
107 Update(ctx context.Context, mID string, peerURLs []string) error
108
109 // Leader gets current leader of the cluster
110 Leader(ctx context.Context) (*Member, error)
111}
112
113type httpMembersAPI struct {
114 client httpClient
115}
116
117func (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
136func (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
168func (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
195func (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
205func (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
224type membersAPIActionList struct{}
225
226func (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
232type membersAPIActionRemove struct {
233 memberID string
234}
235
236func (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
243type membersAPIActionAdd struct {
244 peerURLs types.URLs
245}
246
247func (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
256type membersAPIActionUpdate struct {
257 memberID string
258 peerURLs types.URLs
259}
260
261func (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
271func 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
280type membersAPIActionLeader struct{}
281
282func (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// v2MembersURL add the necessary path to the provided endpoint
290// to route requests to the default v2 members API.
291func v2MembersURL(ep url.URL) *url.URL {
292 ep.Path = path.Join(ep.Path, defaultV2MembersPrefix)
293 return &ep
294}
295
296type membersError struct {
297 Message string `json:"message"`
298 Code int `json:"-"`
299}
300
301func (e membersError) Error() string {
302 return e.Message
303}