members.go 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227
  1. // Copyright 2015 CoreOS, Inc.
  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. package client
  15. import (
  16. "bytes"
  17. "encoding/json"
  18. "fmt"
  19. "net/http"
  20. "net/url"
  21. "path"
  22. "github.com/coreos/etcd/Godeps/_workspace/src/golang.org/x/net/context"
  23. "github.com/coreos/etcd/pkg/types"
  24. )
  25. var (
  26. defaultV2MembersPrefix = "/v2/members"
  27. )
  28. type Member struct {
  29. // ID is the unique identifier of this Member.
  30. ID string `json:"id"`
  31. // Name is a human-readable, non-unique identifier of this Member.
  32. Name string `json:"name"`
  33. // PeerURLs represents the HTTP(S) endpoints this Member uses to
  34. // participate in etcd's consensus protocol.
  35. PeerURLs []string `json:"peerURLs"`
  36. // ClientURLs represents the HTTP(S) endpoints on which this Member
  37. // serves it's client-facing APIs.
  38. ClientURLs []string `json:"clientURLs"`
  39. }
  40. type memberCollection []Member
  41. func (c *memberCollection) UnmarshalJSON(data []byte) error {
  42. d := struct {
  43. Members []Member
  44. }{}
  45. if err := json.Unmarshal(data, &d); err != nil {
  46. return err
  47. }
  48. if d.Members == nil {
  49. *c = make([]Member, 0)
  50. return nil
  51. }
  52. *c = d.Members
  53. return nil
  54. }
  55. type memberCreateRequest struct {
  56. PeerURLs types.URLs
  57. }
  58. func (m *memberCreateRequest) MarshalJSON() ([]byte, error) {
  59. s := struct {
  60. PeerURLs []string `json:"peerURLs"`
  61. }{
  62. PeerURLs: make([]string, len(m.PeerURLs)),
  63. }
  64. for i, u := range m.PeerURLs {
  65. s.PeerURLs[i] = u.String()
  66. }
  67. return json.Marshal(&s)
  68. }
  69. // NewMembersAPI constructs a new MembersAPI that uses HTTP to
  70. // interact with etcd's membership API.
  71. func NewMembersAPI(c Client) MembersAPI {
  72. return &httpMembersAPI{
  73. client: c,
  74. }
  75. }
  76. type MembersAPI interface {
  77. // List enumerates the current cluster membership.
  78. List(ctx context.Context) ([]Member, error)
  79. // Add instructs etcd to accept a new Member into the cluster.
  80. Add(ctx context.Context, peerURL string) (*Member, error)
  81. // Remove demotes an existing Member out of the cluster.
  82. Remove(ctx context.Context, mID string) error
  83. }
  84. type httpMembersAPI struct {
  85. client httpClient
  86. }
  87. func (m *httpMembersAPI) List(ctx context.Context) ([]Member, error) {
  88. req := &membersAPIActionList{}
  89. resp, body, err := m.client.Do(ctx, req)
  90. if err != nil {
  91. return nil, err
  92. }
  93. if err := assertStatusCode(resp.StatusCode, http.StatusOK); err != nil {
  94. return nil, err
  95. }
  96. var mCollection memberCollection
  97. if err := json.Unmarshal(body, &mCollection); err != nil {
  98. return nil, err
  99. }
  100. return []Member(mCollection), nil
  101. }
  102. func (m *httpMembersAPI) Add(ctx context.Context, peerURL string) (*Member, error) {
  103. urls, err := types.NewURLs([]string{peerURL})
  104. if err != nil {
  105. return nil, err
  106. }
  107. req := &membersAPIActionAdd{peerURLs: urls}
  108. resp, body, err := m.client.Do(ctx, req)
  109. if err != nil {
  110. return nil, err
  111. }
  112. if err := assertStatusCode(resp.StatusCode, http.StatusCreated, http.StatusConflict); err != nil {
  113. return nil, err
  114. }
  115. if resp.StatusCode != http.StatusCreated {
  116. var merr membersError
  117. if err := json.Unmarshal(body, &merr); err != nil {
  118. return nil, err
  119. }
  120. return nil, merr
  121. }
  122. var memb Member
  123. if err := json.Unmarshal(body, &memb); err != nil {
  124. return nil, err
  125. }
  126. return &memb, nil
  127. }
  128. func (m *httpMembersAPI) Remove(ctx context.Context, memberID string) error {
  129. req := &membersAPIActionRemove{memberID: memberID}
  130. resp, _, err := m.client.Do(ctx, req)
  131. if err != nil {
  132. return err
  133. }
  134. return assertStatusCode(resp.StatusCode, http.StatusNoContent, http.StatusGone)
  135. }
  136. type membersAPIActionList struct{}
  137. func (l *membersAPIActionList) HTTPRequest(ep url.URL) *http.Request {
  138. u := v2MembersURL(ep)
  139. req, _ := http.NewRequest("GET", u.String(), nil)
  140. return req
  141. }
  142. type membersAPIActionRemove struct {
  143. memberID string
  144. }
  145. func (d *membersAPIActionRemove) HTTPRequest(ep url.URL) *http.Request {
  146. u := v2MembersURL(ep)
  147. u.Path = path.Join(u.Path, d.memberID)
  148. req, _ := http.NewRequest("DELETE", u.String(), nil)
  149. return req
  150. }
  151. type membersAPIActionAdd struct {
  152. peerURLs types.URLs
  153. }
  154. func (a *membersAPIActionAdd) HTTPRequest(ep url.URL) *http.Request {
  155. u := v2MembersURL(ep)
  156. m := memberCreateRequest{PeerURLs: a.peerURLs}
  157. b, _ := json.Marshal(&m)
  158. req, _ := http.NewRequest("POST", u.String(), bytes.NewReader(b))
  159. req.Header.Set("Content-Type", "application/json")
  160. return req
  161. }
  162. func assertStatusCode(got int, want ...int) (err error) {
  163. for _, w := range want {
  164. if w == got {
  165. return nil
  166. }
  167. }
  168. return fmt.Errorf("unexpected status code %d", got)
  169. }
  170. // v2MembersURL add the necessary path to the provided endpoint
  171. // to route requests to the default v2 members API.
  172. func v2MembersURL(ep url.URL) *url.URL {
  173. ep.Path = path.Join(ep.Path, defaultV2MembersPrefix)
  174. return &ep
  175. }
  176. type membersError struct {
  177. Message string `json:"message"`
  178. Code int `json:"-"`
  179. }
  180. func (e membersError) Error() string {
  181. return e.Message
  182. }