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