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  }