go.etcd.io/etcd@v3.3.27+incompatible/client/members.go (about) 1 // 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 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 "github.com/coreos/etcd/pkg/types" 27 ) 28 29 var ( 30 defaultV2MembersPrefix = "/v2/members" 31 defaultLeaderSuffix = "/leader" 32 ) 33 34 type 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 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 // NewMembersAPI constructs a new MembersAPI that uses HTTP to 89 // interact with etcd's membership API. 90 func NewMembersAPI(c Client) MembersAPI { 91 return &httpMembersAPI{ 92 client: c, 93 } 94 } 95 96 type 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 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 // v2MembersURL add the necessary path to the provided endpoint 290 // to route requests to the default v2 members API. 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 }