Skip to content

Commit eed2c85

Browse files
committed
Added handling to "?" and NULL hostnames in CLUSTER SLOTS
1 parent 3d0a6a3 commit eed2c85

2 files changed

Lines changed: 109 additions & 1 deletion

File tree

redis/src/cluster_topology.rs

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,14 +103,20 @@ pub(crate) fn parse_and_count_slots(
103103
if node.len() < 2 {
104104
return None;
105105
}
106-
106+
// According to the CLUSTER SLOTS documentation:
107+
// If the received hostname is an empty string or NULL, clients should utilize the hostname of the responding node.
108+
// However, if the received hostname is "?", it should be regarded as an indication of an unknown node.
107109
let hostname = if let Value::BulkString(ref ip) = node[0] {
108110
let hostname = String::from_utf8_lossy(ip);
109111
if hostname.is_empty() {
110112
addr_of_answering_node.into()
113+
} else if hostname == "?" {
114+
return None;
111115
} else {
112116
hostname
113117
}
118+
} else if let Value::Nil = node[0] {
119+
addr_of_answering_node.into()
114120
} else {
115121
return None;
116122
};
@@ -141,6 +147,13 @@ pub(crate) fn parse_and_count_slots(
141147
slots.push(Slot::new(start, end, nodes.pop().unwrap(), replicas));
142148
}
143149
}
150+
if slots.is_empty() {
151+
return Err(RedisError::from((
152+
ErrorKind::ResponseError,
153+
"Error parsing slots: No healthy node found",
154+
format!("Raw slot map response: {:?}", raw_slot_resp),
155+
)));
156+
}
144157
// we sort the slots, because different nodes in a cluster might return the same slot view
145158
// in different orders, which might cause the views to be considered evaluated as not equal.
146159
slots.sort_unstable_by(|first, second| match first.start().cmp(&second.start()) {

redis/tests/test_cluster.rs

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -294,6 +294,101 @@ fn test_cluster_can_connect_to_server_that_sends_cluster_slots_without_host_name
294294
assert_eq!(value, Ok(Value::Nil));
295295
}
296296

297+
#[test]
298+
fn test_cluster_can_connect_to_server_that_sends_cluster_slots_with_null_host_name() {
299+
let name = "test_cluster_can_connect_to_server_that_sends_cluster_slots_with_null_host_name";
300+
301+
let MockEnv { mut connection, .. } = MockEnv::new(name, move |cmd: &[u8], _| {
302+
if contains_slice(cmd, b"PING") {
303+
Err(Ok(Value::SimpleString("OK".into())))
304+
} else if contains_slice(cmd, b"CLUSTER") && contains_slice(cmd, b"SLOTS") {
305+
Err(Ok(Value::Array(vec![Value::Array(vec![
306+
Value::Int(0),
307+
Value::Int(16383),
308+
Value::Array(vec![Value::Nil, Value::Int(6379)]),
309+
])])))
310+
} else {
311+
Err(Ok(Value::Nil))
312+
}
313+
});
314+
315+
let value = cmd("GET").arg("test").query::<Value>(&mut connection);
316+
317+
assert_eq!(value, Ok(Value::Nil));
318+
}
319+
320+
#[test]
321+
fn test_cluster_cannot_connect_to_server_with_unknown_host_name() {
322+
let name = "test_cluster_cannot_connect_to_server_with_unknown_host_name";
323+
let handler = move |cmd: &[u8], _| {
324+
if contains_slice(cmd, b"PING") {
325+
Err(Ok(Value::SimpleString("OK".into())))
326+
} else if contains_slice(cmd, b"CLUSTER") && contains_slice(cmd, b"SLOTS") {
327+
Err(Ok(Value::Array(vec![Value::Array(vec![
328+
Value::Int(0),
329+
Value::Int(16383),
330+
Value::Array(vec![
331+
Value::BulkString("?".as_bytes().to_vec()),
332+
Value::Int(6379),
333+
]),
334+
])])))
335+
} else {
336+
Err(Ok(Value::Nil))
337+
}
338+
};
339+
let client_builder = ClusterClient::builder(vec![&*format!("redis://{name}")]);
340+
let client = client_builder.build().unwrap();
341+
MOCK_CONN_UTILS.write().unwrap().insert(
342+
name.to_string(),
343+
MockConnectionUtils {
344+
handler: Some(Arc::new(move |cmd, port| handler(cmd, port))),
345+
..Default::default()
346+
},
347+
);
348+
let connection = client.get_generic_connection::<MockConnection>();
349+
assert!(connection.is_err());
350+
let err = connection.err().unwrap();
351+
assert!(err
352+
.to_string()
353+
.contains("Error parsing slots: No healthy node found"))
354+
}
355+
356+
#[test]
357+
fn test_cluster_can_connect_to_server_that_sends_cluster_slots_with_partial_nodes_with_unknown_host_name(
358+
) {
359+
let name = "test_cluster_can_connect_to_server_that_sends_cluster_slots_with_partial_nodes_with_unknown_host_name";
360+
361+
let MockEnv { mut connection, .. } = MockEnv::new(name, move |cmd: &[u8], port| {
362+
if contains_slice(cmd, b"PING") {
363+
Err(Ok(Value::SimpleString("OK".into())))
364+
} else if contains_slice(cmd, b"CLUSTER") && contains_slice(cmd, b"SLOTS") {
365+
Err(Ok(Value::Array(vec![
366+
Value::Array(vec![
367+
Value::Int(0),
368+
Value::Int(7000),
369+
Value::Array(vec![
370+
Value::BulkString(name.as_bytes().to_vec()),
371+
Value::Int(6379),
372+
]),
373+
]),
374+
Value::Array(vec![
375+
Value::Int(7001),
376+
Value::Int(16383),
377+
Value::Array(vec![
378+
Value::BulkString("?".as_bytes().to_vec()),
379+
Value::Int(6380),
380+
]),
381+
]),
382+
])))
383+
} else {
384+
Err(Ok(Value::Nil))
385+
}
386+
});
387+
388+
let value = cmd("GET").arg("test").query::<Value>(&mut connection);
389+
assert_eq!(value, Ok(Value::Nil));
390+
}
391+
297392
#[test]
298393
fn test_cluster_pipeline_command_ordering() {
299394
let cluster = TestClusterContext::new(3, 0);

0 commit comments

Comments
 (0)