etcd_client/rpc/
cluster.rs

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