1// Copyright 2016 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 clientv3
16
17import (
18	"context"
19
20	pb "go.etcd.io/etcd/etcdserver/etcdserverpb"
21	"go.etcd.io/etcd/pkg/types"
22
23	"google.golang.org/grpc"
24)
25
26type (
27	Member                pb.Member
28	MemberListResponse    pb.MemberListResponse
29	MemberAddResponse     pb.MemberAddResponse
30	MemberRemoveResponse  pb.MemberRemoveResponse
31	MemberUpdateResponse  pb.MemberUpdateResponse
32	MemberPromoteResponse pb.MemberPromoteResponse
33)
34
35type Cluster interface {
36	// MemberList lists the current cluster membership.
37	MemberList(ctx context.Context) (*MemberListResponse, error)
38
39	// MemberAdd adds a new member into the cluster.
40	MemberAdd(ctx context.Context, peerAddrs []string) (*MemberAddResponse, error)
41
42	// MemberAddAsLearner adds a new learner member into the cluster.
43	MemberAddAsLearner(ctx context.Context, peerAddrs []string) (*MemberAddResponse, error)
44
45	// MemberRemove removes an existing member from the cluster.
46	MemberRemove(ctx context.Context, id uint64) (*MemberRemoveResponse, error)
47
48	// MemberUpdate updates the peer addresses of the member.
49	MemberUpdate(ctx context.Context, id uint64, peerAddrs []string) (*MemberUpdateResponse, error)
50
51	// MemberPromote promotes a member from raft learner (non-voting) to raft voting member.
52	MemberPromote(ctx context.Context, id uint64) (*MemberPromoteResponse, error)
53}
54
55type cluster struct {
56	remote   pb.ClusterClient
57	callOpts []grpc.CallOption
58}
59
60func NewCluster(c *Client) Cluster {
61	api := &cluster{remote: RetryClusterClient(c)}
62	if c != nil {
63		api.callOpts = c.callOpts
64	}
65	return api
66}
67
68func NewClusterFromClusterClient(remote pb.ClusterClient, c *Client) Cluster {
69	api := &cluster{remote: remote}
70	if c != nil {
71		api.callOpts = c.callOpts
72	}
73	return api
74}
75
76func (c *cluster) MemberAdd(ctx context.Context, peerAddrs []string) (*MemberAddResponse, error) {
77	return c.memberAdd(ctx, peerAddrs, false)
78}
79
80func (c *cluster) MemberAddAsLearner(ctx context.Context, peerAddrs []string) (*MemberAddResponse, error) {
81	return c.memberAdd(ctx, peerAddrs, true)
82}
83
84func (c *cluster) memberAdd(ctx context.Context, peerAddrs []string, isLearner bool) (*MemberAddResponse, error) {
85	// fail-fast before panic in rafthttp
86	if _, err := types.NewURLs(peerAddrs); err != nil {
87		return nil, err
88	}
89
90	r := &pb.MemberAddRequest{
91		PeerURLs:  peerAddrs,
92		IsLearner: isLearner,
93	}
94	resp, err := c.remote.MemberAdd(ctx, r, c.callOpts...)
95	if err != nil {
96		return nil, toErr(ctx, err)
97	}
98	return (*MemberAddResponse)(resp), nil
99}
100
101func (c *cluster) MemberRemove(ctx context.Context, id uint64) (*MemberRemoveResponse, error) {
102	r := &pb.MemberRemoveRequest{ID: id}
103	resp, err := c.remote.MemberRemove(ctx, r, c.callOpts...)
104	if err != nil {
105		return nil, toErr(ctx, err)
106	}
107	return (*MemberRemoveResponse)(resp), nil
108}
109
110func (c *cluster) MemberUpdate(ctx context.Context, id uint64, peerAddrs []string) (*MemberUpdateResponse, error) {
111	// fail-fast before panic in rafthttp
112	if _, err := types.NewURLs(peerAddrs); err != nil {
113		return nil, err
114	}
115
116	// it is safe to retry on update.
117	r := &pb.MemberUpdateRequest{ID: id, PeerURLs: peerAddrs}
118	resp, err := c.remote.MemberUpdate(ctx, r, c.callOpts...)
119	if err == nil {
120		return (*MemberUpdateResponse)(resp), nil
121	}
122	return nil, toErr(ctx, err)
123}
124
125func (c *cluster) MemberList(ctx context.Context) (*MemberListResponse, error) {
126	// it is safe to retry on list.
127	resp, err := c.remote.MemberList(ctx, &pb.MemberListRequest{}, c.callOpts...)
128	if err == nil {
129		return (*MemberListResponse)(resp), nil
130	}
131	return nil, toErr(ctx, err)
132}
133
134func (c *cluster) MemberPromote(ctx context.Context, id uint64) (*MemberPromoteResponse, error) {
135	r := &pb.MemberPromoteRequest{ID: id}
136	resp, err := c.remote.MemberPromote(ctx, r, c.callOpts...)
137	if err != nil {
138		return nil, toErr(ctx, err)
139	}
140	return (*MemberPromoteResponse)(resp), nil
141}
142