Skip to content

Commit 661b90a

Browse files
nmoosciscoNathan Moos
andauthored
feat: allow caller to initialize the channel (#93)
In some scenarios, such as when using a Deterministic Simulation Testing environment such as `turmoil`, when mocking the behaviour of etcd itself, or using non-Tokio sockets for connecting to etcd, it is desirable to construct a Tonic channel externally, then "wrap" it in a `Client` object. This patch enables this use case by adding a `raw-channel` Cargo feature, which enables the `Client::from_channel` function. Co-authored-by: Nathan Moos <nmoos@cisco.com>
1 parent b8f67d1 commit 661b90a

File tree

5 files changed

+35
-5
lines changed

5 files changed

+35
-5
lines changed

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ tls-openssl-vendored = ["tls-openssl", "openssl/vendored"]
1919
tls-roots = ["tls", "tonic/tls-roots"]
2020
pub-response-field = ["visible"]
2121
build-server = ["pub-response-field"]
22+
raw-channel = []
2223

2324
[dependencies]
2425
tonic = "0.12.3"

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ Examples can be found in [`examples`](./examples).
7373
- `tls-openssl`: Enables the `openssl`-based TLS connections. This would make your binary dynamically link to `libssl`.
7474
- `tls-openssl-vendored`: Like `tls-openssl`, however compile openssl from source code and statically link to it.
7575
- `build-server`: Builds a server variant of the etcd protobuf and re-exports it under the same `proto` package as the `pub-response-field` feature does.
76+
- `raw-channel`: Allows the caller to construct the underlying Tonic channel used by the client.
7677

7778
## Test
7879

src/client.rs

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
//! Asynchronous client & synchronous client.
22
3+
#[cfg(feature = "raw-channel")]
4+
use crate::channel::Channel;
35
use crate::error::{Error, Result};
46
use crate::intercept::{InterceptedChannel, Interceptor};
57
use crate::lock::RwLockExt;
@@ -66,7 +68,7 @@ pub struct Client {
6668
cluster: ClusterClient,
6769
election: ElectionClient,
6870
options: Option<ConnectOptions>,
69-
tx: Sender<Change<Uri, Endpoint>>,
71+
tx: Option<Sender<Change<Uri, Endpoint>>>,
7072
}
7173

7274
impl Client {
@@ -130,7 +132,24 @@ impl Client {
130132
let auth_token = Arc::new(RwLock::new(None));
131133
Self::auth(channel.clone(), &mut options, &auth_token).await?;
132134

133-
Ok(Self::build_client(channel, tx, auth_token, options))
135+
Ok(Self::build_client(channel, Some(tx), auth_token, options))
136+
}
137+
138+
#[cfg(feature = "raw-channel")]
139+
/// Connect to `etcd` servers represented by the given `channel`.
140+
pub async fn from_channel(channel: Channel, options: Option<ConnectOptions>) -> Result<Self> {
141+
let channel = InterceptedChannel::new(
142+
channel,
143+
Interceptor {
144+
require_leader: options.as_ref().map(|o| o.require_leader).unwrap_or(false),
145+
},
146+
);
147+
let mut options = options;
148+
149+
let auth_token = Arc::new(RwLock::new(None));
150+
Self::auth(channel.clone(), &mut options, &auth_token).await?;
151+
152+
Ok(Self::build_client(channel, None, auth_token, options))
134153
}
135154

136155
fn build_endpoint(url: &str, options: &Option<ConnectOptions>) -> Result<Endpoint> {
@@ -255,7 +274,7 @@ impl Client {
255274

256275
fn build_client(
257276
channel: InterceptedChannel,
258-
tx: Sender<Change<Uri, Endpoint>>,
277+
tx: Option<Sender<Change<Uri, Endpoint>>>,
259278
auth_token: Arc<RwLock<Option<HeaderValue>>>,
260279
options: Option<ConnectOptions>,
261280
) -> Self {
@@ -295,7 +314,9 @@ impl Client {
295314
#[inline]
296315
pub async fn add_endpoint<E: AsRef<str>>(&self, endpoint: E) -> Result<()> {
297316
let endpoint = Self::build_endpoint(endpoint.as_ref(), &self.options)?;
298-
let tx = &self.tx;
317+
let Some(tx) = &self.tx else {
318+
return Err(Error::EndpointsNotManaged);
319+
};
299320
tx.send(Change::Insert(endpoint.uri().clone(), endpoint))
300321
.await
301322
.map_err(|e| Error::EndpointError(format!("failed to add endpoint because of {}", e)))
@@ -309,7 +330,9 @@ impl Client {
309330
#[inline]
310331
pub async fn remove_endpoint<E: AsRef<str>>(&self, endpoint: E) -> Result<()> {
311332
let uri = http::Uri::from_str(endpoint.as_ref())?;
312-
let tx = &self.tx;
333+
let Some(tx) = &self.tx else {
334+
return Err(Error::EndpointsNotManaged);
335+
};
313336
tx.send(Change::Remove(uri)).await.map_err(|e| {
314337
Error::EndpointError(format!("failed to remove endpoint because of {}", e))
315338
})

src/error.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,9 @@ pub enum Error {
4141
/// Endpoint error
4242
EndpointError(String),
4343

44+
/// Endpoint set is not managed by this client
45+
EndpointsNotManaged,
46+
4447
/// OpenSSL errors.
4548
#[cfg(feature = "tls-openssl")]
4649
OpenSsl(openssl::error::ErrorStack),
@@ -61,6 +64,7 @@ impl Display for Error {
6164
Error::ElectError(e) => write!(f, "election error: {}", e),
6265
Error::InvalidHeaderValue(e) => write!(f, "invalid metadata value: {}", e),
6366
Error::EndpointError(e) => write!(f, "endpoint error: {}", e),
67+
Error::EndpointsNotManaged => write!(f, "endpoints not managed by this client"),
6468
#[cfg(feature = "tls-openssl")]
6569
Error::OpenSsl(e) => write!(f, "open ssl error: {}", e),
6670
}

src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@
5454
//! - `tls-openssl`: Enables the `openssl`-based TLS connections. This would make your binary dynamically link to `libssl`.
5555
//! - `tls-openssl-vendored`: Like `tls-openssl`, however compile openssl from source code and statically link to it.
5656
//! - `build-server`: Builds a server variant of the etcd protobuf and re-exports it under the same `proto` package as the `pub-response-field` feature does.
57+
//! - `raw-channel`: Allows the caller to construct the underlying Tonic channel used by the client.
5758
5859
#![cfg_attr(docsrs, feature(doc_cfg))]
5960

0 commit comments

Comments
 (0)