Skip to main content

etcd_client/
client.rs

1//! Asynchronous client & synchronous client.
2
3use crate::caller::{CallOptions, ClientCaller, ClientCallerBuilder};
4#[cfg(feature = "raw-channel")]
5use crate::channel::Channel;
6use crate::error::{Error, Result};
7use crate::intercept::{InterceptedChannel, Interceptor};
8#[cfg(feature = "tls-openssl")]
9use crate::openssl_tls::{OpenSslClientConfig, OpenSslConnector};
10use crate::rpc::auth::Permission;
11use crate::rpc::auth::RawAuthClient;
12use crate::rpc::auth::{AuthClient, AuthDisableResponse, AuthEnableResponse};
13use crate::rpc::auth::{
14    RoleAddResponse, RoleDeleteResponse, RoleGetResponse, RoleGrantPermissionResponse,
15    RoleListResponse, RoleRevokePermissionOptions, RoleRevokePermissionResponse, UserAddOptions,
16    UserAddResponse, UserChangePasswordResponse, UserDeleteResponse, UserGetResponse,
17    UserGrantRoleResponse, UserListResponse, UserRevokeRoleResponse,
18};
19use crate::rpc::cluster::{
20    ClusterClient, MemberAddOptions, MemberAddResponse, MemberListResponse, MemberPromoteResponse,
21    MemberRemoveResponse, MemberUpdateResponse,
22};
23use crate::rpc::election::{
24    CampaignResponse, ElectionClient, LeaderResponse, ObserveStream, ProclaimOptions,
25    ProclaimResponse, ResignOptions, ResignResponse,
26};
27use crate::rpc::kv::{
28    CompactionOptions, CompactionResponse, DeleteOptions, DeleteResponse, GetOptions, GetResponse,
29    KvClient, PutOptions, PutResponse, Txn, TxnResponse,
30};
31use crate::rpc::lease::{
32    LeaseClient, LeaseGrantOptions, LeaseGrantResponse, LeaseKeepAliveStream, LeaseKeeper,
33    LeaseLeasesResponse, LeaseRevokeResponse, LeaseTimeToLiveOptions, LeaseTimeToLiveResponse,
34};
35use crate::rpc::lock::{LockClient, LockOptions, LockResponse, UnlockResponse};
36use crate::rpc::maintenance::{
37    AlarmAction, AlarmOptions, AlarmResponse, AlarmType, DefragmentResponse, HashKvResponse,
38    HashResponse, MaintenanceClient, MoveLeaderResponse, SnapshotStreaming, StatusResponse,
39};
40use crate::rpc::watch::{WatchClient, WatchOptions, WatchStream};
41#[cfg(feature = "tls-openssl")]
42use crate::OpenSslResult;
43#[cfg(feature = "_tls")]
44use crate::TlsOptions;
45use http::uri::Uri;
46use tonic::metadata::{Ascii, MetadataValue};
47
48use std::str::FromStr;
49use std::sync::{Arc, RwLock};
50use std::time::Duration;
51use tokio::sync::mpsc::Sender;
52
53use tonic::transport::{channel::Change, Endpoint};
54
55const HTTP_PREFIX: &str = "http://";
56const HTTPS_PREFIX: &str = "https://";
57
58pub(crate) type AuthToken = Arc<RwLock<Option<MetadataValue<Ascii>>>>;
59
60/// Asynchronous `etcd` client using v3 API.
61#[derive(Clone)]
62pub struct Client {
63    kv: KvClient,
64    watch: WatchClient,
65    lease: LeaseClient,
66    lock: LockClient,
67    auth: AuthClient,
68    maintenance: MaintenanceClient,
69    cluster: ClusterClient,
70    election: ElectionClient,
71    /// Note: the relevant [`ConnectOptions::user`] maintenance is
72    /// the [`Self::client_caller`] responsibility.
73    options: ConnectOptions,
74    tx: Option<Sender<Change<Uri, Endpoint>>>,
75    client_caller: ClientCaller<()>,
76}
77
78impl Client {
79    /// Connect to `etcd` servers from given `endpoints`.
80    pub async fn connect<E: AsRef<str>, S: AsRef<[E]>>(
81        endpoints: S,
82        options: Option<ConnectOptions>,
83    ) -> Result<Self> {
84        #[cfg(not(feature = "tls-openssl"))]
85        let make_balanced_channel = crate::channel::Tonic;
86        #[cfg(feature = "tls-openssl")]
87        let make_balanced_channel = crate::channel::Openssl {
88            conn: options
89                .clone()
90                .and_then(|o| o.otls)
91                .unwrap_or_else(OpenSslConnector::create_default)?,
92        };
93        Self::connect_with_balanced_channel(endpoints, options, make_balanced_channel).await
94    }
95
96    /// Connect to `etcd` servers from given `endpoints` and a balanced channel.
97    pub async fn connect_with_balanced_channel<E: AsRef<str>, S: AsRef<[E]>, MBC>(
98        endpoints: S,
99        options: Option<ConnectOptions>,
100        make_balanced_channel: MBC,
101    ) -> Result<Self>
102    where
103        MBC: crate::channel::BalancedChannelBuilder,
104        crate::error::Error: From<MBC::Error>,
105    {
106        let options = options.unwrap_or_default();
107        let endpoints = {
108            let mut eps = Vec::new();
109            for e in endpoints.as_ref() {
110                let channel = Self::build_endpoint(e.as_ref(), &options)?;
111                eps.push(channel);
112            }
113            eps
114        };
115
116        if endpoints.is_empty() {
117            return Err(Error::InvalidArgs(String::from("empty endpoints")));
118        }
119
120        let auth_token = Arc::new(RwLock::new(None));
121
122        // Always use balance strategy even if there is only one endpoint.
123        let (channel, tx) = make_balanced_channel.balanced_channel(64)?;
124        let channel = InterceptedChannel::new(
125            channel,
126            Interceptor {
127                require_leader: options.require_leader,
128                auth_token: auth_token.clone(),
129            },
130        );
131        for endpoint in endpoints {
132            // The rx inside `channel` may be closed or error, e.g. the balanced service is
133            // openssl based and the openssl connector is misconfigured, the send here may fail.
134            tx.send(Change::Insert(endpoint.uri().clone(), endpoint))
135                .await
136                .map_err(|_| {
137                    Error::Internal("failed to insert endpoint into the balanced channel".into())
138                })?;
139        }
140
141        let client = Self::build_client(channel, Some(tx), auth_token, options);
142        client.refresh_token().await?;
143        Ok(client)
144    }
145
146    #[cfg(feature = "raw-channel")]
147    /// Connect to `etcd` servers represented by the given `channel`.
148    pub async fn from_channel(channel: Channel, options: Option<ConnectOptions>) -> Result<Self> {
149        let options = options.unwrap_or_default();
150        let auth_token = Arc::new(RwLock::new(None));
151        let channel = InterceptedChannel::new(
152            channel,
153            Interceptor {
154                require_leader: options.require_leader,
155                auth_token: auth_token.clone(),
156            },
157        );
158
159        let client = Self::build_client(channel, None, auth_token, options);
160        client.refresh_token().await?;
161        Ok(client)
162    }
163
164    fn build_endpoint(url: &str, options: &ConnectOptions) -> Result<Endpoint> {
165        use tonic::transport::Channel as TonicChannel;
166        let mut endpoint = if url.starts_with(HTTP_PREFIX) {
167            #[cfg(feature = "_tls")]
168            if options.tls.is_some() {
169                return Err(Error::InvalidArgs(String::from(
170                    "TLS options are only supported with HTTPS URLs",
171                )));
172            }
173
174            TonicChannel::builder(url.parse()?)
175        } else if url.starts_with(HTTPS_PREFIX) {
176            #[cfg(not(any(feature = "_tls", feature = "tls-openssl")))]
177            return Err(Error::InvalidArgs(String::from(
178                "HTTPS URLs are only supported with one of the features \"tls\", \"tls-ring\" or \"tls-aws-lc\"",
179            )));
180
181            #[cfg(all(feature = "tls-openssl", not(feature = "_tls")))]
182            {
183                TonicChannel::builder(url.parse()?)
184            }
185
186            #[cfg(feature = "_tls")]
187            {
188                let tls = options.tls.clone().unwrap_or_default();
189                TonicChannel::builder(url.parse()?).tls_config(tls)?
190            }
191        } else {
192            #[cfg(feature = "_tls")]
193            {
194                let tls = options.tls.clone();
195
196                match tls {
197                    Some(tls) => {
198                        let e = HTTPS_PREFIX.to_owned() + url;
199                        TonicChannel::builder(e.parse()?).tls_config(tls)?
200                    }
201                    None => {
202                        let e = HTTP_PREFIX.to_owned() + url;
203                        TonicChannel::builder(e.parse()?)
204                    }
205                }
206            }
207
208            #[cfg(all(feature = "tls-openssl", not(feature = "_tls")))]
209            {
210                let pfx = if options.otls.as_ref().is_some() {
211                    HTTPS_PREFIX
212                } else {
213                    HTTP_PREFIX
214                };
215                let e = pfx.to_owned() + url;
216                TonicChannel::builder(e.parse()?)
217            }
218
219            #[cfg(all(not(feature = "_tls"), not(feature = "tls-openssl")))]
220            {
221                let e = HTTP_PREFIX.to_owned() + url;
222                TonicChannel::builder(e.parse()?)
223            }
224        };
225
226        if let Some((interval, timeout)) = options.keep_alive {
227            endpoint = endpoint
228                .keep_alive_while_idle(options.keep_alive_while_idle)
229                .http2_keep_alive_interval(interval)
230                .keep_alive_timeout(timeout);
231        }
232
233        if let Some(timeout) = options.timeout {
234            endpoint = endpoint.timeout(timeout);
235        }
236
237        if let Some(timeout) = options.connect_timeout {
238            endpoint = endpoint.connect_timeout(timeout);
239        }
240
241        if let Some(tcp_keepalive) = options.tcp_keepalive {
242            endpoint = endpoint.tcp_keepalive(Some(tcp_keepalive));
243        }
244
245        Ok(endpoint)
246    }
247
248    fn build_client(
249        channel: InterceptedChannel,
250        tx: Option<Sender<Change<Uri, Endpoint>>>,
251        auth_token: Arc<RwLock<Option<MetadataValue<Ascii>>>>,
252        options: ConnectOptions,
253    ) -> Self {
254        let builder = ClientCallerBuilder::new(
255            (&options).into(),
256            auth_token,
257            RawAuthClient::new(channel.clone()),
258            channel,
259        );
260
261        let auth = AuthClient::new(builder.clone());
262        let kv = KvClient::new(builder.clone());
263        let watch = WatchClient::new(builder.clone());
264        let lease = LeaseClient::new(builder.clone());
265        let lock = LockClient::new(builder.clone());
266        let cluster = ClusterClient::new(builder.clone());
267        let maintenance = MaintenanceClient::new(builder.clone());
268        let election = ElectionClient::new(builder.clone());
269
270        Self {
271            kv,
272            watch,
273            lease,
274            lock,
275            auth,
276            maintenance,
277            cluster,
278            election,
279            options,
280            tx,
281            client_caller: builder.build(|_| ()),
282        }
283    }
284
285    /// Dynamically add an endpoint to the client.
286    ///
287    /// Which can be used to add a new member to the underlying balance cache.
288    /// The typical scenario is that application can use a services discovery
289    /// to discover the member list changes and add/remove them to/from the client.
290    ///
291    /// Note that the [`Client`] doesn't check the authentication before added.
292    /// So the etcd member of the added endpoint REQUIRES to use the same auth
293    /// token as when create the client. Otherwise, the underlying balance
294    /// services will not be able to connect to the new endpoint.
295    #[inline]
296    pub async fn add_endpoint<E: AsRef<str>>(&self, endpoint: E) -> Result<()> {
297        let endpoint = Self::build_endpoint(endpoint.as_ref(), &self.options)?;
298        let Some(tx) = &self.tx else {
299            return Err(Error::EndpointsNotManaged);
300        };
301        tx.send(Change::Insert(endpoint.uri().clone(), endpoint))
302            .await
303            .map_err(|e| Error::EndpointError(format!("failed to add endpoint because of {e}")))
304    }
305
306    /// Dynamically remove an endpoint from the client.
307    ///
308    /// Note that the `endpoint` str should be the same as it was added.
309    /// And the underlying balance services cache used the hash from the Uri,
310    /// which was parsed from `endpoint` str, to do the equality comparisons.
311    #[inline]
312    pub async fn remove_endpoint<E: AsRef<str>>(&self, endpoint: E) -> Result<()> {
313        let uri = http::Uri::from_str(endpoint.as_ref())?;
314        let Some(tx) = &self.tx else {
315            return Err(Error::EndpointsNotManaged);
316        };
317        tx.send(Change::Remove(uri))
318            .await
319            .map_err(|e| Error::EndpointError(format!("failed to remove endpoint because of {e}")))
320    }
321
322    /// Gets a KV client.
323    #[inline]
324    pub fn kv_client(&self) -> KvClient {
325        self.kv.clone()
326    }
327
328    /// Gets a watch client.
329    #[inline]
330    pub fn watch_client(&self) -> WatchClient {
331        self.watch.clone()
332    }
333
334    /// Gets a lease client.
335    #[inline]
336    pub fn lease_client(&self) -> LeaseClient {
337        self.lease.clone()
338    }
339
340    /// Gets an auth client.
341    #[inline]
342    pub fn auth_client(&self) -> AuthClient {
343        self.auth.clone()
344    }
345
346    /// Gets a maintenance client.
347    #[inline]
348    pub fn maintenance_client(&self) -> MaintenanceClient {
349        self.maintenance.clone()
350    }
351
352    /// Gets a cluster client.
353    #[inline]
354    pub fn cluster_client(&self) -> ClusterClient {
355        self.cluster.clone()
356    }
357
358    /// Gets a lock client.
359    #[inline]
360    pub fn lock_client(&self) -> LockClient {
361        self.lock.clone()
362    }
363
364    /// Gets a election client.
365    #[inline]
366    pub fn election_client(&self) -> ElectionClient {
367        self.election.clone()
368    }
369
370    /// Put the given key into the key-value store.
371    /// A put request increments the revision of the key-value store
372    /// and generates one event in the event history.
373    #[inline]
374    pub async fn put(
375        &mut self,
376        key: impl Into<Vec<u8>>,
377        value: impl Into<Vec<u8>>,
378        options: Option<PutOptions>,
379    ) -> Result<PutResponse> {
380        self.kv.put(key, value, options).await
381    }
382
383    /// Gets the key from the key-value store.
384    #[inline]
385    pub async fn get(
386        &mut self,
387        key: impl Into<Vec<u8>>,
388        options: Option<GetOptions>,
389    ) -> Result<GetResponse> {
390        self.kv.get(key, options).await
391    }
392
393    /// Deletes the given key from the key-value store.
394    #[inline]
395    pub async fn delete(
396        &mut self,
397        key: impl Into<Vec<u8>>,
398        options: Option<DeleteOptions>,
399    ) -> Result<DeleteResponse> {
400        self.kv.delete(key, options).await
401    }
402
403    /// Compacts the event history in the etcd key-value store. The key-value
404    /// store should be periodically compacted or the event history will continue to grow
405    /// indefinitely.
406    #[inline]
407    pub async fn compact(
408        &mut self,
409        revision: i64,
410        options: Option<CompactionOptions>,
411    ) -> Result<CompactionResponse> {
412        self.kv.compact(revision, options).await
413    }
414
415    /// Processes multiple operations in a single transaction.
416    /// A txn request increments the revision of the key-value store
417    /// and generates events with the same revision for every completed operation.
418    /// It is not allowed to modify the same key several times within one txn.
419    #[inline]
420    pub async fn txn(&mut self, txn: Txn) -> Result<TxnResponse> {
421        self.kv.txn(txn).await
422    }
423
424    /// Watches for events happening or that have happened. Both input and output
425    /// are streams; the input stream is for creating and canceling watcher and the output
426    /// stream sends events. The entire event history can be watched starting from the
427    /// last compaction revision.
428    #[inline]
429    pub async fn watch(
430        &mut self,
431        key: impl Into<Vec<u8>>,
432        options: Option<WatchOptions>,
433    ) -> Result<WatchStream> {
434        self.watch.watch(key, options).await
435    }
436
437    /// Creates a lease which expires if the server does not receive a keepAlive
438    /// within a given time to live period. All keys attached to the lease will be expired and
439    /// deleted if the lease expires. Each expired key generates a delete event in the event history.
440    #[inline]
441    pub async fn lease_grant(
442        &mut self,
443        ttl: i64,
444        options: Option<LeaseGrantOptions>,
445    ) -> Result<LeaseGrantResponse> {
446        self.lease.grant(ttl, options).await
447    }
448
449    /// Revokes a lease. All keys attached to the lease will expire and be deleted.
450    #[inline]
451    pub async fn lease_revoke(&mut self, id: i64) -> Result<LeaseRevokeResponse> {
452        self.lease.revoke(id).await
453    }
454
455    /// Keeps the lease alive by streaming keep alive requests from the client
456    /// to the server and streaming keep alive responses from the server to the client.
457    #[inline]
458    pub async fn lease_keep_alive(
459        &mut self,
460        id: i64,
461    ) -> Result<(LeaseKeeper, LeaseKeepAliveStream)> {
462        self.lease.keep_alive(id).await
463    }
464
465    /// Retrieves lease information.
466    #[inline]
467    pub async fn lease_time_to_live(
468        &mut self,
469        id: i64,
470        options: Option<LeaseTimeToLiveOptions>,
471    ) -> Result<LeaseTimeToLiveResponse> {
472        self.lease.time_to_live(id, options).await
473    }
474
475    /// Lists all existing leases.
476    #[inline]
477    pub async fn leases(&mut self) -> Result<LeaseLeasesResponse> {
478        self.lease.leases().await
479    }
480
481    /// Lock acquires a distributed shared lock on a given named lock.
482    /// On success, it will return a unique key that exists so long as the
483    /// lock is held by the caller. This key can be used in conjunction with
484    /// transactions to safely ensure updates to etcd only occur while holding
485    /// lock ownership. The lock is held until Unlock is called on the key or the
486    /// lease associate with the owner expires.
487    #[inline]
488    pub async fn lock(
489        &mut self,
490        name: impl Into<Vec<u8>>,
491        options: Option<LockOptions>,
492    ) -> Result<LockResponse> {
493        self.lock.lock(name, options).await
494    }
495
496    /// Unlock takes a key returned by Lock and releases the hold on lock. The
497    /// next Lock caller waiting for the lock will then be woken up and given
498    /// ownership of the lock.
499    #[inline]
500    pub async fn unlock(&mut self, key: impl Into<Vec<u8>>) -> Result<UnlockResponse> {
501        self.lock.unlock(key).await
502    }
503
504    /// Enables authentication.
505    #[inline]
506    pub async fn auth_enable(&mut self) -> Result<AuthEnableResponse> {
507        self.auth.auth_enable().await
508    }
509
510    /// Disables authentication.
511    #[inline]
512    pub async fn auth_disable(&mut self) -> Result<AuthDisableResponse> {
513        self.auth.auth_disable().await
514    }
515
516    /// Adds role.
517    #[inline]
518    pub async fn role_add(&mut self, name: impl Into<String>) -> Result<RoleAddResponse> {
519        self.auth.role_add(name).await
520    }
521
522    /// Deletes role.
523    #[inline]
524    pub async fn role_delete(&mut self, name: impl Into<String>) -> Result<RoleDeleteResponse> {
525        self.auth.role_delete(name).await
526    }
527
528    /// Gets role.
529    #[inline]
530    pub async fn role_get(&mut self, name: impl Into<String>) -> Result<RoleGetResponse> {
531        self.auth.role_get(name).await
532    }
533
534    /// Lists role.
535    #[inline]
536    pub async fn role_list(&mut self) -> Result<RoleListResponse> {
537        self.auth.role_list().await
538    }
539
540    /// Grants role permission.
541    #[inline]
542    pub async fn role_grant_permission(
543        &mut self,
544        name: impl Into<String>,
545        perm: Permission,
546    ) -> Result<RoleGrantPermissionResponse> {
547        self.auth.role_grant_permission(name, perm).await
548    }
549
550    /// Revokes role permission.
551    #[inline]
552    pub async fn role_revoke_permission(
553        &mut self,
554        name: impl Into<String>,
555        key: impl Into<Vec<u8>>,
556        options: Option<RoleRevokePermissionOptions>,
557    ) -> Result<RoleRevokePermissionResponse> {
558        self.auth.role_revoke_permission(name, key, options).await
559    }
560
561    /// Add an user.
562    #[inline]
563    pub async fn user_add(
564        &mut self,
565        name: impl Into<String>,
566        password: impl Into<String>,
567        options: Option<UserAddOptions>,
568    ) -> Result<UserAddResponse> {
569        self.auth.user_add(name, password, options).await
570    }
571
572    /// Gets the user info by the user name.
573    #[inline]
574    pub async fn user_get(&mut self, name: impl Into<String>) -> Result<UserGetResponse> {
575        self.auth.user_get(name).await
576    }
577
578    /// Lists all users.
579    #[inline]
580    pub async fn user_list(&mut self) -> Result<UserListResponse> {
581        self.auth.user_list().await
582    }
583
584    /// Deletes the given key from the key-value store.
585    #[inline]
586    pub async fn user_delete(&mut self, name: impl Into<String>) -> Result<UserDeleteResponse> {
587        self.auth.user_delete(name).await
588    }
589
590    /// Change password for an user.
591    #[inline]
592    pub async fn user_change_password(
593        &mut self,
594        name: impl Into<String>,
595        password: impl Into<String>,
596    ) -> Result<UserChangePasswordResponse> {
597        self.auth.user_change_password(name, password).await
598    }
599
600    /// Grant role for an user.
601    #[inline]
602    pub async fn user_grant_role(
603        &mut self,
604        user: impl Into<String>,
605        role: impl Into<String>,
606    ) -> Result<UserGrantRoleResponse> {
607        self.auth.user_grant_role(user, role).await
608    }
609
610    /// Revoke role for an user.
611    #[inline]
612    pub async fn user_revoke_role(
613        &mut self,
614        user: impl Into<String>,
615        role: impl Into<String>,
616    ) -> Result<UserRevokeRoleResponse> {
617        self.auth.user_revoke_role(user, role).await
618    }
619
620    /// Maintain(get, active or inactive) alarms of members.
621    #[inline]
622    pub async fn alarm(
623        &mut self,
624        alarm_action: AlarmAction,
625        alarm_type: AlarmType,
626        options: Option<AlarmOptions>,
627    ) -> Result<AlarmResponse> {
628        self.maintenance
629            .alarm(alarm_action, alarm_type, options)
630            .await
631    }
632
633    /// Gets the status of a member.
634    #[inline]
635    pub async fn status(&mut self) -> Result<StatusResponse> {
636        self.maintenance.status().await
637    }
638
639    /// Defragments a member's backend database to recover storage space.
640    #[inline]
641    pub async fn defragment(&mut self) -> Result<DefragmentResponse> {
642        self.maintenance.defragment().await
643    }
644
645    /// Computes the hash of whole backend keyspace.
646    /// including key, lease, and other buckets in storage.
647    /// This is designed for testing ONLY!
648    #[inline]
649    pub async fn hash(&mut self) -> Result<HashResponse> {
650        self.maintenance.hash().await
651    }
652
653    /// Computes the hash of all MVCC keys up to a given revision.
654    /// It only iterates \"key\" bucket in backend storage.
655    #[inline]
656    pub async fn hash_kv(&mut self, revision: i64) -> Result<HashKvResponse> {
657        self.maintenance.hash_kv(revision).await
658    }
659
660    /// Gets a snapshot of the entire backend from a member over a stream to a client.
661    #[inline]
662    pub async fn snapshot(&mut self) -> Result<SnapshotStreaming> {
663        self.maintenance.snapshot().await
664    }
665
666    /// Adds current connected server as a member.
667    #[inline]
668    pub async fn member_add<E: AsRef<str>, S: AsRef<[E]>>(
669        &mut self,
670        urls: S,
671        options: Option<MemberAddOptions>,
672    ) -> Result<MemberAddResponse> {
673        let mut eps = Vec::new();
674        for e in urls.as_ref() {
675            let e = e.as_ref();
676            let url = if e.starts_with(HTTP_PREFIX) || e.starts_with(HTTPS_PREFIX) {
677                e.to_string()
678            } else {
679                HTTP_PREFIX.to_owned() + e
680            };
681            eps.push(url);
682        }
683
684        self.cluster.member_add(eps, options).await
685    }
686
687    /// Remove a member.
688    #[inline]
689    pub async fn member_remove(&mut self, id: u64) -> Result<MemberRemoveResponse> {
690        self.cluster.member_remove(id).await
691    }
692
693    /// Updates the member.
694    #[inline]
695    pub async fn member_update(
696        &mut self,
697        id: u64,
698        url: impl Into<Vec<String>>,
699    ) -> Result<MemberUpdateResponse> {
700        self.cluster.member_update(id, url).await
701    }
702
703    /// Promotes the member.
704    #[inline]
705    pub async fn member_promote(&mut self, id: u64) -> Result<MemberPromoteResponse> {
706        self.cluster.member_promote(id).await
707    }
708
709    /// Lists members.
710    #[inline]
711    pub async fn member_list(&mut self) -> Result<MemberListResponse> {
712        self.cluster.member_list().await
713    }
714
715    /// Moves the current leader node to target node.
716    #[inline]
717    pub async fn move_leader(&mut self, target_id: u64) -> Result<MoveLeaderResponse> {
718        self.maintenance.move_leader(target_id).await
719    }
720
721    /// Puts a value as eligible for the election on the prefix key.
722    /// Multiple sessions can participate in the election for the
723    /// same prefix, but only one can be the leader at a time.
724    #[inline]
725    pub async fn campaign(
726        &mut self,
727        name: impl Into<Vec<u8>>,
728        value: impl Into<Vec<u8>>,
729        lease: i64,
730    ) -> Result<CampaignResponse> {
731        self.election.campaign(name, value, lease).await
732    }
733
734    /// Lets the leader announce a new value without another election.
735    #[inline]
736    pub async fn proclaim(
737        &mut self,
738        value: impl Into<Vec<u8>>,
739        options: Option<ProclaimOptions>,
740    ) -> Result<ProclaimResponse> {
741        self.election.proclaim(value, options).await
742    }
743
744    /// Returns the leader value for the current election.
745    #[inline]
746    pub async fn leader(&mut self, name: impl Into<Vec<u8>>) -> Result<LeaderResponse> {
747        self.election.leader(name).await
748    }
749
750    /// Returns a channel that reliably observes ordered leader proposals
751    /// as GetResponse values on every current elected leader key.
752    #[inline]
753    pub async fn observe(&mut self, name: impl Into<Vec<u8>>) -> Result<ObserveStream> {
754        self.election.observe(name).await
755    }
756
757    /// Releases election leadership and then start a new election
758    #[inline]
759    pub async fn resign(&mut self, option: Option<ResignOptions>) -> Result<ResignResponse> {
760        self.election.resign(option).await
761    }
762
763    /// Refresh the authentication token if the client has credentials options.
764    pub async fn refresh_token(&self) -> Result<()> {
765        self.client_caller.refresh_token().await
766    }
767
768    /// Updates the user credentials for the client in flight.
769    ///
770    /// Client will perform the authentication with the given user credentials. If successful, the
771    /// authentication token will be updated in the client. Nothing happens if the authentication
772    /// fails.
773    ///
774    /// If the user is `None`, it will remove the authentication token from the client.
775    pub async fn update_user(&mut self, user: Option<(String, String)>) -> Result<()> {
776        self.client_caller.update_user(user).await
777    }
778}
779
780/// Options for `Connect` operation.
781#[derive(Debug, Default, Clone)]
782pub struct ConnectOptions {
783    /// user is a pair values of name and password
784    user: Option<(String, String)>,
785    /// HTTP2 keep-alive: (keep_alive_interval, keep_alive_timeout)
786    keep_alive: Option<(Duration, Duration)>,
787    /// Whether send keep alive pings even there are no active streams.
788    keep_alive_while_idle: bool,
789    /// Apply a timeout to each gRPC request.
790    timeout: Option<Duration>,
791    /// Apply a timeout to connecting to the endpoint.
792    connect_timeout: Option<Duration>,
793    /// TCP keepalive.
794    tcp_keepalive: Option<Duration>,
795    #[cfg(feature = "_tls")]
796    tls: Option<TlsOptions>,
797    #[cfg(feature = "tls-openssl")]
798    otls: Option<OpenSslResult<OpenSslConnector>>,
799    /// Require a leader to be present for the operation to complete.
800    require_leader: bool,
801    /// Automatically refresh the authentication token on expiration.
802    refresh_expired_token: bool,
803}
804
805impl ConnectOptions {
806    /// name is the identifier for the distributed shared lock to be acquired.
807    #[inline]
808    pub fn with_user(mut self, name: impl Into<String>, password: impl Into<String>) -> Self {
809        self.user = Some((name.into(), password.into()));
810        self
811    }
812
813    /// Sets TLS options.
814    ///
815    /// Notes that this function have to work with `HTTPS` URLs.
816    #[cfg_attr(docsrs, doc(cfg(any(feature = "tls-ring", feature = "tls-aws-lc"))))]
817    #[cfg(feature = "_tls")]
818    #[inline]
819    pub fn with_tls(mut self, tls: TlsOptions) -> Self {
820        self.tls = Some(tls);
821        self
822    }
823
824    /// Sets TLS options, however using the OpenSSL implementation.
825    #[cfg_attr(docsrs, doc(cfg(feature = "tls-openssl")))]
826    #[cfg(feature = "tls-openssl")]
827    #[inline]
828    pub fn with_openssl_tls(mut self, otls: OpenSslClientConfig) -> Self {
829        // NOTE1: Perhaps we can unify the essential TLS config terms by something like `TlsBuilder`?
830        //
831        // NOTE2: we delay the checking at connection step to keep consistency with tonic, however would
832        // things be better if we validate the config at here?
833        self.otls = Some(otls.build());
834        self
835    }
836
837    /// Enable HTTP2 keep-alive with `interval` and `timeout`.
838    #[inline]
839    pub fn with_keep_alive(mut self, interval: Duration, timeout: Duration) -> Self {
840        self.keep_alive = Some((interval, timeout));
841        self
842    }
843
844    /// Apply a timeout to each request.
845    #[inline]
846    pub fn with_timeout(mut self, timeout: Duration) -> Self {
847        self.timeout = Some(timeout);
848        self
849    }
850
851    /// Apply a timeout to connecting to the endpoint.
852    #[inline]
853    pub fn with_connect_timeout(mut self, timeout: Duration) -> Self {
854        self.connect_timeout = Some(timeout);
855        self
856    }
857
858    /// Enable TCP keepalive.
859    #[inline]
860    pub fn with_tcp_keepalive(mut self, tcp_keepalive: Duration) -> Self {
861        self.tcp_keepalive = Some(tcp_keepalive);
862        self
863    }
864
865    /// Whether send keep alive pings even there are no active requests.
866    /// If disabled, keep-alive pings are only sent while there are opened request/response streams.
867    /// If enabled, pings are also sent when no streams are active.
868    /// NOTE: Some implementations of gRPC server may send GOAWAY if there are too many pings.
869    ///       This would be useful if you meet some error like `too many pings`.
870    #[inline]
871    pub fn with_keep_alive_while_idle(mut self, enabled: bool) -> Self {
872        self.keep_alive_while_idle = enabled;
873        self
874    }
875
876    /// Whether to enforce that a leader be present in the etcd cluster.
877    #[inline]
878    pub fn with_require_leader(mut self, require_leader: bool) -> Self {
879        self.require_leader = require_leader;
880        self
881    }
882
883    /// Whether to automatically refresh the authentication token when the current
884    /// token has expired. Note that, when this feature is enabled, each request is
885    /// *CLONED* so that it can be retried.
886    ///
887    /// Concurrent requests which find the token expired refresh it once. If the refresh keeps
888    /// failing, e.g. the password of the user was changed, the requests waiting for it
889    /// authenticate one after another and each of them fails, so the last one waits for about
890    /// as many round trips as there are requests.
891    pub fn with_auto_token_refresh(mut self, refresh_expired_token: bool) -> Self {
892        self.refresh_expired_token = refresh_expired_token;
893        self
894    }
895
896    /// Creates a `ConnectOptions`.
897    #[inline]
898    pub const fn new() -> Self {
899        ConnectOptions {
900            user: None,
901            keep_alive: None,
902            keep_alive_while_idle: true,
903            timeout: None,
904            connect_timeout: None,
905            tcp_keepalive: None,
906            #[cfg(feature = "_tls")]
907            tls: None,
908            #[cfg(feature = "tls-openssl")]
909            otls: None,
910            require_leader: false,
911            refresh_expired_token: false,
912        }
913    }
914}
915
916impl From<&ConnectOptions> for CallOptions {
917    fn from(options: &ConnectOptions) -> Self {
918        Self {
919            creds: Arc::new(RwLock::new(options.user.clone())),
920            refresh_expired_token: options.refresh_expired_token,
921            refresh_lock: Default::default(),
922        }
923    }
924}