Skip to content

Commit 8b3fb0a

Browse files
Merge pull request #74 from nihohit/cluster-resp3
Add RESP3 support to cluster connections.
2 parents 270b999 + 7cb3aec commit 8b3fb0a

File tree

6 files changed

+98
-12
lines changed

6 files changed

+98
-12
lines changed

redis/src/cluster.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -910,6 +910,7 @@ pub(crate) fn get_connection_info(
910910
redis: RedisConnectionInfo {
911911
password: cluster_params.password,
912912
username: cluster_params.username,
913+
use_resp3: cluster_params.use_resp3,
913914
..Default::default()
914915
},
915916
})

redis/src/cluster_client.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ struct BuilderParams {
3333
retries_configuration: RetryParams,
3434
connection_timeout: Option<Duration>,
3535
topology_checks_interval: Option<Duration>,
36+
use_resp3: bool,
3637
}
3738

3839
#[derive(Clone)]
@@ -86,6 +87,7 @@ pub(crate) struct ClusterParams {
8687
pub(crate) connection_timeout: Duration,
8788
pub(crate) topology_checks_interval: Option<Duration>,
8889
pub(crate) tls_params: Option<TlsConnParams>,
90+
pub(crate) use_resp3: bool,
8991
}
9092

9193
impl ClusterParams {
@@ -109,6 +111,7 @@ impl ClusterParams {
109111
connection_timeout: value.connection_timeout.unwrap_or(Duration::MAX),
110112
topology_checks_interval: value.topology_checks_interval,
111113
tls_params,
114+
use_resp3: value.use_resp3,
112115
})
113116
}
114117
}
@@ -315,6 +318,15 @@ impl ClusterClientBuilder {
315318
self
316319
}
317320

321+
/// Sets whether the new ClusterClient should connect to the servers using RESP3.
322+
///
323+
/// If not set, the default is to use RESP3.
324+
#[cfg(any(feature = "tls-native-tls", feature = "tls-rustls"))]
325+
pub fn use_resp3(mut self, use_resp3: bool) -> ClusterClientBuilder {
326+
self.builder_params.use_resp3 = use_resp3;
327+
self
328+
}
329+
318330
/// Use `build()`.
319331
#[deprecated(since = "0.22.0", note = "Use build()")]
320332
pub fn open(self) -> RedisResult<ClusterClient> {

redis/tests/support/cluster.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ use tempfile::TempDir;
1616

1717
use crate::support::{build_keys_and_certs_for_tls, Module};
1818

19+
use super::use_resp3;
1920
#[cfg(feature = "tls-rustls")]
2021
use super::{build_single_client, load_certs_from_file};
2122

@@ -343,6 +344,7 @@ impl TestClusterContext {
343344
.map(RedisServer::connection_info)
344345
.collect();
345346
let mut builder = redis::cluster::ClusterClientBuilder::new(initial_nodes.clone());
347+
builder = builder.use_resp3(use_resp3());
346348

347349
#[cfg(feature = "tls-rustls")]
348350
if mtls_enabled {

redis/tests/support/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ use redis::{ClientTlsConfig, TlsCertificates};
2020
use socket2::{Domain, Socket, Type};
2121
use tempfile::TempDir;
2222

23-
fn use_resp3() -> bool {
23+
pub fn use_resp3() -> bool {
2424
env::var("RESP3").unwrap_or_default() == "true"
2525
}
2626

redis/tests/test_cluster.rs

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,38 @@ fn test_cluster_multi_shard_commands() {
136136
assert_eq!(res, vec!["bazz", "bar", "foo"]);
137137
}
138138

139+
#[test]
140+
fn test_cluster_resp3() {
141+
if !use_resp3() {
142+
return;
143+
}
144+
let cluster = TestClusterContext::new(3, 0);
145+
146+
let mut connection = cluster.connection();
147+
148+
let hello: std::collections::HashMap<String, Value> =
149+
redis::cmd("HELLO").query(&mut connection).unwrap();
150+
assert_eq!(hello.get("proto").unwrap(), &Value::Int(3));
151+
152+
let _: () = connection.hset("hash", "foo", "baz").unwrap();
153+
let _: () = connection.hset("hash", "bar", "foobar").unwrap();
154+
let result: Value = connection.hgetall("hash").unwrap();
155+
156+
assert_eq!(
157+
result,
158+
Value::Map(vec![
159+
(
160+
Value::BulkString("foo".as_bytes().to_vec()),
161+
Value::BulkString("baz".as_bytes().to_vec())
162+
),
163+
(
164+
Value::BulkString("bar".as_bytes().to_vec()),
165+
Value::BulkString("foobar".as_bytes().to_vec())
166+
)
167+
])
168+
);
169+
}
170+
139171
#[test]
140172
#[cfg(feature = "script")]
141173
fn test_cluster_script() {

redis/tests/test_cluster_async.rs

Lines changed: 50 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,42 @@ fn test_async_cluster_route_info_to_nodes() {
203203
.unwrap();
204204
}
205205

206+
#[test]
207+
fn test_cluster_resp3() {
208+
if !use_resp3() {
209+
return;
210+
}
211+
block_on_all(async move {
212+
let cluster = TestClusterContext::new(3, 0);
213+
214+
let mut connection = cluster.async_connection().await;
215+
216+
let hello: HashMap<String, Value> = redis::cmd("HELLO")
217+
.query_async(&mut connection)
218+
.await
219+
.unwrap();
220+
assert_eq!(hello.get("proto").unwrap(), &Value::Int(3));
221+
222+
let _: () = connection.hset("hash", "foo", "baz").await.unwrap();
223+
let _: () = connection.hset("hash", "bar", "foobar").await.unwrap();
224+
let result: Value = connection.hgetall("hash").await.unwrap();
225+
226+
assert_eq!(
227+
result,
228+
Value::Map(vec![
229+
(
230+
Value::BulkString("foo".as_bytes().to_vec()),
231+
Value::BulkString("baz".as_bytes().to_vec())
232+
),
233+
(
234+
Value::BulkString("bar".as_bytes().to_vec()),
235+
Value::BulkString("foobar".as_bytes().to_vec())
236+
)
237+
])
238+
);
239+
});
240+
}
241+
206242
#[ignore] // TODO Handle pipe where the keys do not all go to the same node
207243
#[test]
208244
fn test_async_cluster_basic_pipe() {
@@ -1854,21 +1890,24 @@ fn test_async_cluster_round_robin_read_from_replica() {
18541890
fn get_queried_node_id_if_master(cluster_nodes_output: Value) -> Option<String> {
18551891
// Returns the node ID of the connection that was queried for CLUSTER NODES (using the 'myself' flag), if it's a master.
18561892
// Otherwise, returns None.
1893+
let get_node_id = |str: &str| {
1894+
let parts: Vec<&str> = str.split('\n').collect();
1895+
for node_entry in parts {
1896+
if node_entry.contains("myself") && node_entry.contains("master") {
1897+
let node_entry_parts: Vec<&str> = node_entry.split(' ').collect();
1898+
let node_id = node_entry_parts[0];
1899+
return Some(node_id.to_string());
1900+
}
1901+
}
1902+
None
1903+
};
1904+
18571905
match cluster_nodes_output {
18581906
Value::BulkString(val) => match from_utf8(&val) {
1859-
Ok(str_res) => {
1860-
let parts: Vec<&str> = str_res.split('\n').collect();
1861-
for node_entry in parts {
1862-
if node_entry.contains("myself") && node_entry.contains("master") {
1863-
let node_entry_parts: Vec<&str> = node_entry.split(' ').collect();
1864-
let node_id = node_entry_parts[0];
1865-
return Some(node_id.to_string());
1866-
}
1867-
}
1868-
None
1869-
}
1907+
Ok(str_res) => get_node_id(str_res),
18701908
Err(e) => panic!("failed to decode INFO response: {:?}", e),
18711909
},
1910+
Value::VerbatimString { format: _, text } => get_node_id(&text),
18721911
_ => panic!("Recieved unexpected response: {:?}", cluster_nodes_output),
18731912
}
18741913
}

0 commit comments

Comments
 (0)