etcd_client/rpc/
cluster.rs

1//! Etcd Cluster RPC.
2
3use crate::error::Result;
4use crate::intercept::InterceptedChannel;
5use crate::rpc::pb::etcdserverpb::cluster_client::ClusterClient as PbClusterClient;
6use crate::rpc::pb::etcdserverpb::{
7    Member as PbMember, MemberAddRequest as PbMemberAddRequest,
8    MemberAddResponse as PbMemberAddResponse, MemberListRequest as PbMemberListRequest,
9    MemberListResponse as PbMemberListResponse, MemberPromoteRequest as PbMemberPromoteRequest,
10    MemberPromoteResponse as PbMemberPromoteResponse, MemberRemoveRequest as PbMemberRemoveRequest,
11    MemberRemoveResponse as PbMemberRemoveResponse, MemberUpdateRequest as PbMemberUpdateRequest,
12    MemberUpdateResponse as PbMemberUpdateResponse,
13};
14use crate::rpc::ResponseHeader;
15use tonic::{IntoRequest, Request};
16
17/// Client for Cluster operations.
18#[repr(transparent)]
19#[derive(Clone)]
20pub struct ClusterClient {
21    inner: PbClusterClient<InterceptedChannel>,
22}
23
24impl ClusterClient {
25    /// Creates an Cluster client.
26    #[inline]
27    pub(crate) fn new(channel: InterceptedChannel) -> Self {
28        let inner = PbClusterClient::new(channel);
29        Self { inner }
30    }
31
32    /// Adds a new member into the cluster.
33    #[inline]
34    pub async fn member_add(
35        &mut self,
36        urls: impl Into<Vec<String>>,
37        options: Option<MemberAddOptions>,
38    ) -> Result<MemberAddResponse> {
39        let resp = self
40            .inner
41            .member_add(options.unwrap_or_default().with_urls(urls))
42            .await?
43            .into_inner();
44
45        Ok(MemberAddResponse::new(resp))
46    }
47
48    /// Removes an existing member from the cluster.
49    #[inline]
50    pub async fn member_remove(&mut self, id: u64) -> Result<MemberRemoveResponse> {
51        let resp = self
52            .inner
53            .member_remove(MemberRemoveOptions::new().with_id(id))
54            .await?
55            .into_inner();
56        Ok(MemberRemoveResponse::new(resp))
57    }
58
59    /// Updates the member configuration.
60    #[inline]
61    pub async fn member_update(
62        &mut self,
63        id: u64,
64        url: impl Into<Vec<String>>,
65    ) -> Result<MemberUpdateResponse> {
66        let resp = self
67            .inner
68            .member_update(MemberUpdateOptions::new().with_option(id, url))
69            .await?
70            .into_inner();
71        Ok(MemberUpdateResponse::new(resp))
72    }
73
74    /// Lists all the members in the cluster.
75    #[inline]
76    pub async fn member_list(&mut self) -> Result<MemberListResponse> {
77        let resp = self
78            .inner
79            .member_list(PbMemberListRequest {})
80            .await?
81            .into_inner();
82        Ok(MemberListResponse::new(resp))
83    }
84
85    /// Promotes a member from raft learner (non-voting) to raft voting member.
86    #[inline]
87    pub async fn member_promote(&mut self, id: u64) -> Result<MemberPromoteResponse> {
88        let resp = self
89            .inner
90            .member_promote(MemberPromoteOptions::new().with_id(id))
91            .await?
92            .into_inner();
93        Ok(MemberPromoteResponse::new(resp))
94    }
95}
96
97/// Options for `MemberAdd` operation.
98#[derive(Debug, Default, Clone)]
99#[repr(transparent)]
100pub struct MemberAddOptions(PbMemberAddRequest);
101
102impl MemberAddOptions {
103    #[inline]
104    fn with_urls(mut self, urls: impl Into<Vec<String>>) -> Self {
105        self.0.peer_ur_ls = urls.into();
106        self
107    }
108
109    /// Creates a `MemberAddOptions`.
110    #[inline]
111    pub const fn new() -> Self {
112        Self(PbMemberAddRequest {
113            peer_ur_ls: Vec::new(),
114            is_learner: false,
115        })
116    }
117
118    /// Sets the member as a learner.
119    #[inline]
120    pub const fn with_is_learner(mut self) -> Self {
121        self.0.is_learner = true;
122        self
123    }
124}
125
126impl From<MemberAddOptions> for PbMemberAddRequest {
127    #[inline]
128    fn from(options: MemberAddOptions) -> Self {
129        options.0
130    }
131}
132
133impl IntoRequest<PbMemberAddRequest> for MemberAddOptions {
134    #[inline]
135    fn into_request(self) -> Request<PbMemberAddRequest> {
136        Request::new(self.into())
137    }
138}
139
140/// Response for `MemberAdd` operation.
141#[cfg_attr(feature = "pub-response-field", visible::StructFields(pub))]
142#[derive(Debug, Default, Clone)]
143#[repr(transparent)]
144pub struct MemberAddResponse(PbMemberAddResponse);
145
146impl MemberAddResponse {
147    /// Create a new `MemberAddResponse` from pb cluster response.
148    #[inline]
149    const fn new(resp: PbMemberAddResponse) -> Self {
150        Self(resp)
151    }
152
153    /// Get response header.
154    #[inline]
155    pub fn header(&self) -> Option<&ResponseHeader> {
156        self.0.header.as_ref().map(From::from)
157    }
158
159    /// Get the member information of the added member.
160    #[inline]
161    pub fn member(&self) -> Option<&Member> {
162        self.0.member.as_ref().map(From::from)
163    }
164
165    /// Takes the header out of the response, leaving a [`None`] in its place.
166    #[inline]
167    pub fn take_header(&mut self) -> Option<ResponseHeader> {
168        self.0.header.take().map(ResponseHeader::new)
169    }
170
171    /// Get the member list after adding the new member.
172    #[inline]
173    pub fn member_list(&self) -> &[Member] {
174        unsafe { &*(self.0.members.as_slice() as *const _ as *const [Member]) }
175    }
176}
177
178/// Options for `MemberRemove` operation.
179#[derive(Debug, Default, Clone)]
180// #[repr(transparent)]
181pub struct MemberRemoveOptions(PbMemberRemoveRequest);
182
183impl MemberRemoveOptions {
184    /// Set id
185    #[inline]
186    fn with_id(mut self, id: u64) -> Self {
187        self.0.id = id;
188        self
189    }
190
191    /// Creates a `MemberRemoveOptions`.
192    #[inline]
193    pub const fn new() -> Self {
194        Self(PbMemberRemoveRequest { id: 0 })
195    }
196}
197
198impl From<MemberRemoveOptions> for PbMemberRemoveRequest {
199    #[inline]
200    fn from(options: MemberRemoveOptions) -> Self {
201        options.0
202    }
203}
204
205impl IntoRequest<PbMemberRemoveRequest> for MemberRemoveOptions {
206    #[inline]
207    fn into_request(self) -> Request<PbMemberRemoveRequest> {
208        Request::new(self.into())
209    }
210}
211
212/// Response for `MemberRemove` operation.
213#[cfg_attr(feature = "pub-response-field", visible::StructFields(pub))]
214#[derive(Debug, Default, Clone)]
215#[repr(transparent)]
216pub struct MemberRemoveResponse(PbMemberRemoveResponse);
217
218impl MemberRemoveResponse {
219    /// Create a new `MemberRemoveResponse` from pb cluster response.
220    #[inline]
221    const fn new(resp: PbMemberRemoveResponse) -> Self {
222        Self(resp)
223    }
224
225    /// Get response header.
226    #[inline]
227    pub fn header(&self) -> Option<&ResponseHeader> {
228        self.0.header.as_ref().map(From::from)
229    }
230
231    /// Takes the header out of the response, leaving a [`None`] in its place.
232    #[inline]
233    pub fn take_header(&mut self) -> Option<ResponseHeader> {
234        self.0.header.take().map(ResponseHeader::new)
235    }
236
237    /// A list of all members after removing the member
238    #[inline]
239    pub fn members(&self) -> &[Member] {
240        unsafe { &*(self.0.members.as_slice() as *const _ as *const [Member]) }
241    }
242}
243
244/// Options for `MemberUpdate` operation.
245#[derive(Debug, Default, Clone)]
246// #[repr(transparent)]
247pub struct MemberUpdateOptions(PbMemberUpdateRequest);
248
249impl MemberUpdateOptions {
250    #[inline]
251    fn with_option(mut self, id: u64, url: impl Into<Vec<String>>) -> Self {
252        self.0.id = id;
253        self.0.peer_ur_ls = url.into();
254        self
255    }
256
257    /// Creates a `MemberUpdateOptions`.
258    #[inline]
259    pub const fn new() -> Self {
260        Self(PbMemberUpdateRequest {
261            id: 0,
262            peer_ur_ls: Vec::new(),
263        })
264    }
265}
266
267impl From<MemberUpdateOptions> for PbMemberUpdateRequest {
268    #[inline]
269    fn from(options: MemberUpdateOptions) -> Self {
270        options.0
271    }
272}
273
274impl IntoRequest<PbMemberUpdateRequest> for MemberUpdateOptions {
275    #[inline]
276    fn into_request(self) -> Request<PbMemberUpdateRequest> {
277        Request::new(self.into())
278    }
279}
280
281/// Response for `MemberUpdate` operation.
282#[cfg_attr(feature = "pub-response-field", visible::StructFields(pub))]
283#[derive(Debug, Default, Clone)]
284#[repr(transparent)]
285pub struct MemberUpdateResponse(PbMemberUpdateResponse);
286
287impl MemberUpdateResponse {
288    /// Create a new `MemberUpdateResponse` from pb cluster response.
289    #[inline]
290    const fn new(resp: PbMemberUpdateResponse) -> Self {
291        Self(resp)
292    }
293
294    /// Get response header.
295    #[inline]
296    pub fn header(&self) -> Option<&ResponseHeader> {
297        self.0.header.as_ref().map(From::from)
298    }
299
300    /// Takes the header out of the response, leaving a [`None`] in its place.
301    #[inline]
302    pub fn take_header(&mut self) -> Option<ResponseHeader> {
303        self.0.header.take().map(ResponseHeader::new)
304    }
305
306    /// A list of all members after updating the member.
307    #[inline]
308    pub fn members(&self) -> &[Member] {
309        unsafe { &*(self.0.members.as_slice() as *const _ as *const [Member]) }
310    }
311}
312
313/// Response for `MemberList` operation.
314#[cfg_attr(feature = "pub-response-field", visible::StructFields(pub))]
315#[derive(Debug, Clone)]
316#[repr(transparent)]
317pub struct MemberListResponse(PbMemberListResponse);
318
319impl MemberListResponse {
320    /// Creates a new `MemberListResponse` from pb Member List response.
321    #[inline]
322    const fn new(resp: PbMemberListResponse) -> Self {
323        Self(resp)
324    }
325
326    /// Get response header.
327    #[inline]
328    pub fn header(&self) -> Option<&ResponseHeader> {
329        self.0.header.as_ref().map(From::from)
330    }
331
332    /// Takes the header out of the response, leaving a [`None`] in its place.
333    #[inline]
334    pub fn take_header(&mut self) -> Option<ResponseHeader> {
335        self.0.header.take().map(ResponseHeader::new)
336    }
337
338    /// A list of all members associated with the cluster.
339    #[inline]
340    pub fn members(&self) -> &[Member] {
341        unsafe { &*(self.0.members.as_slice() as *const _ as *const [Member]) }
342    }
343}
344
345/// Cluster member.
346#[cfg_attr(feature = "pub-response-field", visible::StructFields(pub))]
347#[derive(Debug, Clone, PartialEq)]
348#[repr(transparent)]
349pub struct Member(PbMember);
350
351impl Member {
352    /// Member id.
353    #[inline]
354    pub const fn id(&self) -> u64 {
355        self.0.id
356    }
357
358    /// The human-readable name of the member. If the member is not started, the name will be an empty string.
359    #[inline]
360    pub fn name(&self) -> &str {
361        &self.0.name
362    }
363
364    /// The list of URLs the member exposes to the cluster for communication.
365    #[inline]
366    pub fn peer_urls(&self) -> &[String] {
367        &self.0.peer_ur_ls
368    }
369
370    /// The list of URLs the member exposes to clients for communication. If the member is not started, client URLs will be empty.
371    #[inline]
372    pub fn client_urls(&self) -> &[String] {
373        &self.0.client_ur_ls
374    }
375
376    /// Indicates if the member is raft learner.
377    #[inline]
378    pub const fn is_learner(&self) -> bool {
379        self.0.is_learner
380    }
381}
382
383impl From<&PbMember> for &Member {
384    #[inline]
385    fn from(src: &PbMember) -> Self {
386        unsafe { &*(src as *const _ as *const Member) }
387    }
388}
389
390/// Options for `MemberPromote` operation.
391#[derive(Debug, Default, Clone)]
392#[repr(transparent)]
393pub struct MemberPromoteOptions(PbMemberPromoteRequest);
394
395impl MemberPromoteOptions {
396    /// Set id
397    #[inline]
398    fn with_id(mut self, id: u64) -> Self {
399        self.0.id = id;
400        self
401    }
402
403    /// Creates a `MemberPromoteOptions`.
404    #[inline]
405    pub const fn new() -> Self {
406        Self(PbMemberPromoteRequest { id: 0 })
407    }
408}
409
410impl From<MemberPromoteOptions> for PbMemberPromoteRequest {
411    #[inline]
412    fn from(options: MemberPromoteOptions) -> Self {
413        options.0
414    }
415}
416
417impl IntoRequest<PbMemberPromoteRequest> for MemberPromoteOptions {
418    #[inline]
419    fn into_request(self) -> Request<PbMemberPromoteRequest> {
420        Request::new(self.into())
421    }
422}
423
424/// Response for `MemberPromote` operation.
425#[cfg_attr(feature = "pub-response-field", visible::StructFields(pub))]
426#[derive(Debug, Default, Clone)]
427#[repr(transparent)]
428pub struct MemberPromoteResponse(PbMemberPromoteResponse);
429
430impl MemberPromoteResponse {
431    /// Create a new `MemberPromoteResponse` from pb cluster response.
432    #[inline]
433    const fn new(resp: PbMemberPromoteResponse) -> Self {
434        Self(resp)
435    }
436
437    /// Get response header.
438    #[inline]
439    pub fn header(&self) -> Option<&ResponseHeader> {
440        self.0.header.as_ref().map(From::from)
441    }
442
443    /// Takes the header out of the response, leaving a [`None`] in its place.
444    #[inline]
445    pub fn take_header(&mut self) -> Option<ResponseHeader> {
446        self.0.header.take().map(ResponseHeader::new)
447    }
448
449    /// A list of all members after promoting the member.
450    #[inline]
451    pub fn members(&self) -> &[Member] {
452        unsafe { &*(self.0.members.as_slice() as *const _ as *const [Member]) }
453    }
454}