Skip to main content

etcd_client/rpc/
maintenance.rs

1//! Etcd Maintenance RPC.
2
3use crate::caller::{ClientCaller, ClientCallerBuilder};
4pub use crate::rpc::pb::etcdserverpb::alarm_request::AlarmAction;
5pub use crate::rpc::pb::etcdserverpb::downgrade_request::DowngradeAction;
6pub use crate::rpc::pb::etcdserverpb::AlarmType;
7
8use super::pb::etcdserverpb;
9use crate::error::Result;
10use crate::intercept::InterceptedChannel;
11use crate::rpc::pb::etcdserverpb::{
12    AlarmRequest as PbAlarmRequest, AlarmResponse as PbAlarmResponse,
13    DefragmentRequest as PbDefragmentRequest, DefragmentResponse as PbDefragmentResponse,
14    DowngradeInfo as PbDowngradeInfo, DowngradeRequest as PbDowngradeRequest,
15    DowngradeResponse as PbDowngradeResponse, HashKvRequest as PbHashKvRequest,
16    HashKvResponse as PbHashKvResponse, HashRequest as PbHashRequest,
17    HashResponse as PbHashResponse, MoveLeaderRequest as PbMoveLeaderRequest,
18    MoveLeaderResponse as PbMoveLeaderResponse, SnapshotRequest as PbSnapshotRequest,
19    SnapshotResponse as PbSnapshotResponse, StatusRequest as PbStatusRequest,
20    StatusResponse as PbStatusResponse,
21};
22use crate::rpc::ResponseHeader;
23use etcdserverpb::maintenance_client::MaintenanceClient as PbMaintenanceClient;
24use etcdserverpb::AlarmMember as PbAlarmMember;
25use tonic::codec::Streaming as PbStreaming;
26use tonic::{IntoRequest, Request};
27
28type Client = PbMaintenanceClient<InterceptedChannel>;
29
30/// Client for maintenance operations.
31#[repr(transparent)]
32#[derive(Clone)]
33pub struct MaintenanceClient {
34    inner: ClientCaller<Client>,
35}
36
37/// Options for `alarm` operation.
38#[derive(Debug, Default, Clone)]
39#[repr(transparent)]
40pub struct AlarmOptions(PbAlarmRequest);
41
42impl AlarmOptions {
43    /// Creates a new `AlarmOptions`.
44    #[inline]
45    pub const fn new() -> Self {
46        AlarmOptions(PbAlarmRequest {
47            action: AlarmAction::Get as i32,
48            member_id: 0,
49            alarm: AlarmType::None as i32,
50        })
51    }
52
53    /// Sets alarm action and alarm type.
54    #[inline]
55    const fn with_action_and_type(
56        mut self,
57        alarm_action: AlarmAction,
58        alarm_type: AlarmType,
59    ) -> Self {
60        self.0.action = alarm_action as i32;
61        self.0.alarm = alarm_type as i32;
62        self
63    }
64
65    /// Sets alarm member.
66    #[inline]
67    pub fn with_member(&mut self, member: u64) {
68        self.0.member_id = member;
69    }
70}
71
72impl From<AlarmOptions> for PbAlarmRequest {
73    #[inline]
74    fn from(alarm: AlarmOptions) -> Self {
75        alarm.0
76    }
77}
78
79impl IntoRequest<PbAlarmRequest> for AlarmOptions {
80    #[inline]
81    fn into_request(self) -> Request<PbAlarmRequest> {
82        Request::new(self.into())
83    }
84}
85
86/// Options for `Downgrade` operation.
87#[derive(Debug, Clone, PartialEq, Eq, Hash)]
88pub enum DowngradeOptions {
89    /// Validate downgrade capability before starting downgrade, with the given target version
90    Validate(String),
91
92    /// Start a downgrade action to cluster to the given target version
93    Enable(String),
94
95    /// Cancel the ongoing downgrade action to cluster
96    Cancel,
97}
98
99impl From<DowngradeOptions> for PbDowngradeRequest {
100    #[inline]
101    fn from(options: DowngradeOptions) -> Self {
102        match options {
103            DowngradeOptions::Validate(version) => PbDowngradeRequest {
104                action: DowngradeAction::Validate as i32,
105                version,
106            },
107            DowngradeOptions::Enable(version) => PbDowngradeRequest {
108                action: DowngradeAction::Enable as i32,
109                version,
110            },
111            DowngradeOptions::Cancel => PbDowngradeRequest {
112                action: DowngradeAction::Cancel as i32,
113                version: String::new(),
114            },
115        }
116    }
117}
118
119impl IntoRequest<PbDowngradeRequest> for DowngradeOptions {
120    #[inline]
121    fn into_request(self) -> Request<PbDowngradeRequest> {
122        Request::new(self.into())
123    }
124}
125
126/// Downgrade information of cluster.
127#[cfg_attr(feature = "pub-response-field", visible::StructFields(pub))]
128#[derive(Debug, Clone)]
129#[repr(transparent)]
130pub struct DowngradeInfo(PbDowngradeInfo);
131
132impl DowngradeInfo {
133    /// Enabled indicates whether the cluster is enabled to downgrade.
134    #[inline]
135    pub fn enabled(&self) -> bool {
136        self.0.enabled
137    }
138
139    /// Get target version of downgrade.
140    #[inline]
141    pub fn target_version(&self) -> &str {
142        &self.0.target_version
143    }
144}
145
146impl From<&PbDowngradeInfo> for &DowngradeInfo {
147    #[inline]
148    fn from(info: &PbDowngradeInfo) -> Self {
149        unsafe { &*(info as *const _ as *const DowngradeInfo) }
150    }
151}
152
153/// Options for `status` operation.
154#[derive(Debug, Default, Clone)]
155struct StatusOptions(PbStatusRequest);
156
157impl StatusOptions {
158    #[inline]
159    const fn new() -> Self {
160        Self(PbStatusRequest {})
161    }
162}
163
164impl From<StatusOptions> for PbStatusRequest {
165    #[inline]
166    fn from(status: StatusOptions) -> Self {
167        status.0
168    }
169}
170
171impl IntoRequest<PbStatusRequest> for StatusOptions {
172    #[inline]
173    fn into_request(self) -> Request<PbStatusRequest> {
174        Request::new(self.into())
175    }
176}
177
178/// Options for `defragment` operation.
179#[derive(Debug, Default, Clone)]
180struct DefragmentOptions(PbDefragmentRequest);
181
182impl DefragmentOptions {
183    #[inline]
184    const fn new() -> Self {
185        Self(PbDefragmentRequest {})
186    }
187}
188
189impl From<DefragmentOptions> for PbDefragmentRequest {
190    #[inline]
191    fn from(defragment: DefragmentOptions) -> Self {
192        defragment.0
193    }
194}
195
196impl IntoRequest<PbDefragmentRequest> for DefragmentOptions {
197    #[inline]
198    fn into_request(self) -> Request<PbDefragmentRequest> {
199        Request::new(self.into())
200    }
201}
202
203/// Options for `hash` operation.
204#[derive(Debug, Default, Clone)]
205struct HashOptions(PbHashRequest);
206
207impl HashOptions {
208    #[inline]
209    const fn new() -> Self {
210        Self(PbHashRequest {})
211    }
212}
213
214impl From<HashOptions> for PbHashRequest {
215    #[inline]
216    fn from(hash: HashOptions) -> Self {
217        hash.0
218    }
219}
220
221impl IntoRequest<PbHashRequest> for HashOptions {
222    #[inline]
223    fn into_request(self) -> Request<PbHashRequest> {
224        Request::new(self.into())
225    }
226}
227
228/// Options for `hashkv` operation.
229#[derive(Debug, Default, Clone)]
230#[repr(transparent)]
231struct HashKvOptions(PbHashKvRequest);
232
233impl HashKvOptions {
234    #[inline]
235    const fn new(revision: i64) -> Self {
236        Self(PbHashKvRequest { revision })
237    }
238}
239
240impl From<HashKvOptions> for PbHashKvRequest {
241    #[inline]
242    fn from(hash_kv: HashKvOptions) -> Self {
243        hash_kv.0
244    }
245}
246
247impl IntoRequest<PbHashKvRequest> for HashKvOptions {
248    #[inline]
249    fn into_request(self) -> Request<PbHashKvRequest> {
250        Request::new(self.into())
251    }
252}
253
254/// Options for `snapshot` operation.
255#[derive(Debug, Default, Clone)]
256struct SnapshotOptions(PbSnapshotRequest);
257
258impl SnapshotOptions {
259    #[inline]
260    const fn new() -> Self {
261        Self(PbSnapshotRequest {})
262    }
263}
264
265impl From<SnapshotOptions> for PbSnapshotRequest {
266    #[inline]
267    fn from(snapshot: SnapshotOptions) -> Self {
268        snapshot.0
269    }
270}
271
272impl IntoRequest<PbSnapshotRequest> for SnapshotOptions {
273    #[inline]
274    fn into_request(self) -> Request<PbSnapshotRequest> {
275        Request::new(self.into())
276    }
277}
278
279/// Response for `alarm` operation.
280#[cfg_attr(feature = "pub-response-field", visible::StructFields(pub))]
281#[derive(Debug, Clone)]
282#[repr(transparent)]
283pub struct AlarmResponse(PbAlarmResponse);
284
285/// Alarm member of respond.
286#[cfg_attr(feature = "pub-response-field", visible::StructFields(pub))]
287#[derive(Clone, PartialEq, Eq)]
288pub struct AlarmMember {
289    /// memberID is the ID of the member associated with the raised alarm.
290    member_id: u64,
291    /// alarm is the type of alarm which has been raised.
292    alarm: AlarmType,
293}
294
295impl AlarmMember {
296    /// Get member id.
297    #[inline]
298    pub fn member_id(&self) -> u64 {
299        self.member_id
300    }
301
302    /// Get alarm.
303    #[inline]
304    pub fn alarm(&self) -> AlarmType {
305        self.alarm
306    }
307}
308
309impl AlarmResponse {
310    /// Create a new `AlarmResponse` from pb put response.
311    #[inline]
312    const fn new(resp: PbAlarmResponse) -> Self {
313        Self(resp)
314    }
315
316    /// Get response header.
317    #[inline]
318    pub fn header(&self) -> Option<&ResponseHeader> {
319        self.0.header.as_ref().map(From::from)
320    }
321
322    /// Takes the header out of the response, leaving a [`None`] in its place.
323    #[inline]
324    pub fn take_header(&mut self) -> Option<ResponseHeader> {
325        self.0.header.take().map(ResponseHeader::new)
326    }
327
328    /// Get alarms of members.
329    #[inline]
330    pub fn alarms(&self) -> &[AlarmMember] {
331        unsafe { &*(&self.0.alarms as *const Vec<PbAlarmMember> as *const Vec<AlarmMember>) }
332    }
333}
334
335/// Response for `status` operation.
336#[cfg_attr(feature = "pub-response-field", visible::StructFields(pub))]
337#[derive(Debug, Clone)]
338#[repr(transparent)]
339pub struct StatusResponse(PbStatusResponse);
340
341impl StatusResponse {
342    /// Create a new `StatusResponse` from pb put response.
343    #[inline]
344    const fn new(resp: PbStatusResponse) -> Self {
345        Self(resp)
346    }
347
348    /// Get response header.
349    #[inline]
350    pub fn header(&self) -> Option<&ResponseHeader> {
351        self.0.header.as_ref().map(From::from)
352    }
353
354    /// Takes the header out of the response, leaving a [`None`] in its place.
355    #[inline]
356    pub fn take_header(&mut self) -> Option<ResponseHeader> {
357        self.0.header.take().map(ResponseHeader::new)
358    }
359
360    /// Get version of the member.
361    #[inline]
362    pub fn version(&self) -> &str {
363        &self.0.version
364    }
365
366    /// Get size of db, in bytes.
367    #[inline]
368    pub fn db_size(&self) -> i64 {
369        self.0.db_size
370    }
371
372    /// Get leader of cluster.
373    #[inline]
374    pub fn leader(&self) -> u64 {
375        self.0.leader
376    }
377
378    /// Get raft index of cluster.
379    #[inline]
380    pub fn raft_index(&self) -> u64 {
381        self.0.raft_index
382    }
383
384    /// Get raft term of cluster.
385    #[inline]
386    pub fn raft_term(&self) -> u64 {
387        self.0.raft_term
388    }
389
390    /// Get raft applied of respond member.
391    #[inline]
392    pub fn raft_applied_index(&self) -> u64 {
393        self.0.raft_applied_index
394    }
395
396    /// Get errors of cluster members.
397    #[inline]
398    pub fn errors(&self) -> &[String] {
399        &self.0.errors
400    }
401
402    /// Get raft used db size, in bytes.
403    #[inline]
404    pub fn raft_used_db_size(&self) -> i64 {
405        self.0.db_size_in_use
406    }
407
408    /// Indicate if the member is raft learner.
409    #[inline]
410    pub fn is_learner(&self) -> bool {
411        self.0.is_learner
412    }
413
414    /// storageVersion is the version of the db file.
415    /// It might be updated with delay in relationship to the target cluster version.
416    ///
417    /// Supported since etcd 3.6.
418    #[inline]
419    pub fn storage_version(&self) -> &str {
420        &self.0.storage_version
421    }
422
423    /// dbSizeQuota is the configured etcd storage quota in bytes
424    /// (the value passed to etcd instance by flag --quota-backend-bytes)
425    ///
426    /// Supported since etcd 3.6.
427    #[inline]
428    pub fn db_size_quota(&self) -> i64 {
429        self.0.db_size_quota
430    }
431
432    /// downgradeInfo indicates if there is downgrade process.
433    ///
434    /// Supported since etcd 3.6.
435    #[inline]
436    pub fn downgrade_info(&self) -> Option<&DowngradeInfo> {
437        self.0.downgrade_info.as_ref().map(From::from)
438    }
439}
440
441/// Response for `defragment` operation.
442#[cfg_attr(feature = "pub-response-field", visible::StructFields(pub))]
443#[derive(Debug, Clone)]
444#[repr(transparent)]
445pub struct DefragmentResponse(PbDefragmentResponse);
446
447impl DefragmentResponse {
448    /// Create a new `DefragmentResponse` from pb put response.
449    #[inline]
450    const fn new(resp: PbDefragmentResponse) -> Self {
451        Self(resp)
452    }
453
454    /// Get response header.
455    #[inline]
456    pub fn header(&self) -> Option<&ResponseHeader> {
457        self.0.header.as_ref().map(From::from)
458    }
459
460    /// Takes the header out of the response, leaving a [`None`] in its place.
461    #[inline]
462    pub fn take_header(&mut self) -> Option<ResponseHeader> {
463        self.0.header.take().map(ResponseHeader::new)
464    }
465}
466
467/// Response for `hash` operation.
468#[cfg_attr(feature = "pub-response-field", visible::StructFields(pub))]
469#[derive(Debug, Clone)]
470#[repr(transparent)]
471pub struct HashResponse(PbHashResponse);
472
473impl HashResponse {
474    /// Create a new `HashResponse` from pb put response.
475    #[inline]
476    const fn new(resp: PbHashResponse) -> Self {
477        Self(resp)
478    }
479
480    /// Get response header.
481    #[inline]
482    pub fn header(&self) -> Option<&ResponseHeader> {
483        self.0.header.as_ref().map(From::from)
484    }
485
486    /// Takes the header out of the response, leaving a [`None`] in its place.
487    #[inline]
488    pub fn take_header(&mut self) -> Option<ResponseHeader> {
489        self.0.header.take().map(ResponseHeader::new)
490    }
491
492    /// Gets the hash value computed from the responding member's KV's backend.
493    #[inline]
494    pub fn hash(&self) -> u32 {
495        self.0.hash
496    }
497}
498
499/// Response for `hash_kv` operation.
500#[cfg_attr(feature = "pub-response-field", visible::StructFields(pub))]
501#[derive(Debug, Clone)]
502#[repr(transparent)]
503pub struct HashKvResponse(PbHashKvResponse);
504
505impl HashKvResponse {
506    /// Create a new `HashKvResponse` from pb put response.
507    #[inline]
508    const fn new(resp: PbHashKvResponse) -> Self {
509        Self(resp)
510    }
511
512    /// Get response header.
513    #[inline]
514    pub fn header(&self) -> Option<&ResponseHeader> {
515        self.0.header.as_ref().map(From::from)
516    }
517
518    /// Takes the header out of the response, leaving a [`None`] in its place.
519    #[inline]
520    pub fn take_header(&mut self) -> Option<ResponseHeader> {
521        self.0.header.take().map(ResponseHeader::new)
522    }
523
524    /// Gets the hash value computed from the responding member's MVCC keys up to a given revision.
525    #[inline]
526    pub fn hash(&self) -> u32 {
527        self.0.hash
528    }
529
530    /// Gets compacted revision of key-value store when hash begins.
531    #[inline]
532    pub fn compact_version(&self) -> i64 {
533        self.0.compact_revision
534    }
535
536    /// Gets the revision up to which the hash is calculated.
537    ///
538    /// Supported since etcd 3.6.
539    #[inline]
540    pub fn hash_revision(&self) -> i64 {
541        self.0.hash_revision
542    }
543}
544
545/// Response for `snapshot` operation.
546#[cfg_attr(feature = "pub-response-field", visible::StructFields(pub))]
547#[derive(Debug, Clone)]
548#[repr(transparent)]
549pub struct SnapshotResponse(PbSnapshotResponse);
550
551impl SnapshotResponse {
552    /// Create a new `SnapshotResponse` from pb put response.
553    #[inline]
554    const fn new(resp: PbSnapshotResponse) -> Self {
555        Self(resp)
556    }
557
558    /// Get response header.
559    #[inline]
560    pub fn header(&self) -> Option<&ResponseHeader> {
561        self.0.header.as_ref().map(From::from)
562    }
563
564    /// Takes the header out of the response, leaving a [`None`] in its place.
565    #[inline]
566    pub fn take_header(&mut self) -> Option<ResponseHeader> {
567        self.0.header.take().map(ResponseHeader::new)
568    }
569
570    /// Get remaining bytes.
571    #[inline]
572    pub fn remaining_bytes(&self) -> u64 {
573        self.0.remaining_bytes
574    }
575
576    /// The next chunk of the snapshot in the snapshot stream.
577    #[inline]
578    pub fn blob(&self) -> &[u8] {
579        &self.0.blob
580    }
581
582    /// local version of server that created the snapshot.
583    /// In cluster with binaries with different version, each cluster can return different result.
584    /// Informs which etcd server version should be used when restoring the snapshot.
585    ///
586    /// Supported since etcd 3.6.
587    #[inline]
588    pub fn version(&self) -> &str {
589        &self.0.version
590    }
591}
592
593/// Response for `snapshot` operation.
594#[cfg_attr(feature = "pub-response-field", visible::StructFields(pub))]
595#[derive(Debug)]
596#[repr(transparent)]
597pub struct SnapshotStreaming(PbStreaming<PbSnapshotResponse>);
598
599impl SnapshotStreaming {
600    /// Fetches the next message from this stream.
601    #[inline]
602    pub async fn message(&mut self) -> Result<Option<SnapshotResponse>> {
603        let ret = self.0.message().await?;
604        match ret {
605            Some(rsp) => Ok(Some(SnapshotResponse::new(rsp))),
606            None => Ok(None),
607        }
608    }
609}
610
611/// Options for `MoveLeader` operation.
612#[derive(Debug, Default, Clone)]
613#[repr(transparent)]
614pub struct MoveLeaderOptions(PbMoveLeaderRequest);
615
616impl MoveLeaderOptions {
617    /// Sets target_id
618    #[inline]
619    const fn with_target_id(mut self, target_id: u64) -> Self {
620        self.0.target_id = target_id;
621        self
622    }
623
624    /// Creates a `MoveLeaderOptions`.
625    #[inline]
626    pub const fn new() -> Self {
627        Self(PbMoveLeaderRequest { target_id: 0 })
628    }
629}
630
631impl From<MoveLeaderOptions> for PbMoveLeaderRequest {
632    #[inline]
633    fn from(options: MoveLeaderOptions) -> Self {
634        options.0
635    }
636}
637
638impl IntoRequest<PbMoveLeaderRequest> for MoveLeaderOptions {
639    #[inline]
640    fn into_request(self) -> Request<PbMoveLeaderRequest> {
641        Request::new(self.into())
642    }
643}
644
645/// Response for `MoveLeader` operation.
646#[cfg_attr(feature = "pub-response-field", visible::StructFields(pub))]
647#[derive(Debug, Clone)]
648#[repr(transparent)]
649pub struct MoveLeaderResponse(PbMoveLeaderResponse);
650
651impl MoveLeaderResponse {
652    #[inline]
653    const fn new(resp: PbMoveLeaderResponse) -> Self {
654        Self(resp)
655    }
656
657    /// Get response header.
658    #[inline]
659    pub fn header(&self) -> Option<&ResponseHeader> {
660        self.0.header.as_ref().map(From::from)
661    }
662
663    /// Takes the header out of the response, leaving a [`None`] in its place.
664    #[inline]
665    pub fn take_header(&mut self) -> Option<ResponseHeader> {
666        self.0.header.take().map(ResponseHeader::new)
667    }
668}
669
670/// Response for `Downgrade` operation.
671#[cfg_attr(feature = "pub-response-field", visible::StructFields(pub))]
672#[derive(Debug, Clone)]
673#[repr(transparent)]
674pub struct DowngradeResponse(PbDowngradeResponse);
675
676impl DowngradeResponse {
677    #[inline]
678    const fn new(resp: PbDowngradeResponse) -> Self {
679        Self(resp)
680    }
681
682    /// Get response header.
683    #[inline]
684    pub fn header(&self) -> Option<&ResponseHeader> {
685        self.0.header.as_ref().map(From::from)
686    }
687
688    /// Takes the header out of the response, leaving a [`None`] in its place.
689    #[inline]
690    pub fn take_header(&mut self) -> Option<ResponseHeader> {
691        self.0.header.take().map(ResponseHeader::new)
692    }
693
694    /// Get the current cluster version.
695    #[inline]
696    pub fn version(&self) -> &str {
697        &self.0.version
698    }
699}
700
701impl MaintenanceClient {
702    /// Creates a maintenance client.
703    #[inline]
704    pub(crate) fn new(builder: ClientCallerBuilder) -> Self {
705        Self {
706            inner: builder.build(Client::new),
707        }
708    }
709
710    /// Get or active or inactive alarm.
711    #[inline]
712    pub async fn alarm(
713        &mut self,
714        alarm_action: AlarmAction,
715        alarm_type: AlarmType,
716        options: Option<AlarmOptions>,
717    ) -> Result<AlarmResponse> {
718        async fn alarm_impl(client: &mut Client, options: AlarmOptions) -> Result<AlarmResponse> {
719            let resp = client.alarm(options).await?.into_inner();
720            Ok(AlarmResponse::new(resp))
721        }
722        self.inner
723            .do_call(
724                options
725                    .unwrap_or_default()
726                    .with_action_and_type(alarm_action, alarm_type),
727                alarm_impl,
728            )
729            .await
730    }
731
732    /// Get status of a member.
733    #[inline]
734    pub async fn status(&mut self) -> Result<StatusResponse> {
735        async fn status_impl(
736            client: &mut Client,
737            options: StatusOptions,
738        ) -> Result<StatusResponse> {
739            let resp = client.status(options).await?.into_inner();
740            Ok(StatusResponse::new(resp))
741        }
742        self.inner.do_call(StatusOptions::new(), status_impl).await
743    }
744
745    /// Defragment a member's backend database to recover storage space.
746    #[inline]
747    pub async fn defragment(&mut self) -> Result<DefragmentResponse> {
748        async fn defragment_impl(
749            client: &mut Client,
750            options: DefragmentOptions,
751        ) -> Result<DefragmentResponse> {
752            let resp = client.defragment(options).await?.into_inner();
753            Ok(DefragmentResponse::new(resp))
754        }
755        self.inner
756            .do_call(DefragmentOptions::new(), defragment_impl)
757            .await
758    }
759
760    /// Computes the hash of whole backend keyspace.
761    /// including key, lease, and other buckets in storage.
762    /// This is designed for testing ONLY!
763    #[inline]
764    pub async fn hash(&mut self) -> Result<HashResponse> {
765        async fn hash_impl(client: &mut Client, options: HashOptions) -> Result<HashResponse> {
766            let resp = client.hash(options).await?.into_inner();
767            Ok(HashResponse::new(resp))
768        }
769        self.inner.do_call(HashOptions::new(), hash_impl).await
770    }
771
772    /// Computes the hash of all MVCC keys up to a given revision.
773    /// It only iterates \"key\" bucket in backend storage.
774    #[inline]
775    pub async fn hash_kv(&mut self, revision: i64) -> Result<HashKvResponse> {
776        async fn hash_kv_impl(
777            client: &mut Client,
778            options: HashKvOptions,
779        ) -> Result<HashKvResponse> {
780            let resp = client.hash_kv(options).await?.into_inner();
781            Ok(HashKvResponse::new(resp))
782        }
783        self.inner
784            .do_call(HashKvOptions::new(revision), hash_kv_impl)
785            .await
786    }
787
788    /// Gets a snapshot of the entire backend from a member over a stream to a client.
789    #[inline]
790    pub async fn snapshot(&mut self) -> Result<SnapshotStreaming> {
791        async fn snapshot_impl(
792            client: &mut Client,
793            options: SnapshotOptions,
794        ) -> Result<SnapshotStreaming> {
795            let resp = client.snapshot(options).await?.into_inner();
796            Ok(SnapshotStreaming(resp))
797        }
798        self.inner
799            .do_call(SnapshotOptions::new(), snapshot_impl)
800            .await
801    }
802
803    /// Moves the current leader node to target node.
804    #[inline]
805    pub async fn move_leader(&mut self, target_id: u64) -> Result<MoveLeaderResponse> {
806        async fn move_leader_impl(
807            client: &mut Client,
808            options: MoveLeaderOptions,
809        ) -> Result<MoveLeaderResponse> {
810            let resp = client.move_leader(options).await?.into_inner();
811            Ok(MoveLeaderResponse::new(resp))
812        }
813        self.inner
814            .do_call(
815                MoveLeaderOptions::new().with_target_id(target_id),
816                move_leader_impl,
817            )
818            .await
819    }
820
821    /// Downgrade requests downgrades, verifies feasibility or cancels downgrade
822    /// on the cluster version.
823    ///
824    /// Supported since etcd 3.5.
825    #[inline]
826    pub async fn downgrade(&mut self, options: DowngradeOptions) -> Result<DowngradeResponse> {
827        async fn downgrade_impl(
828            client: &mut Client,
829            options: DowngradeOptions,
830        ) -> Result<DowngradeResponse> {
831            let resp = client.downgrade(options).await?.into_inner();
832            Ok(DowngradeResponse::new(resp))
833        }
834        self.inner.do_call(options, downgrade_impl).await
835    }
836}