1use std::io;
14use std::time::Duration;
15
16use kevy_hash::key_hash_slot;
17use kevy_resp::Reply;
18use kevy_resp_client::RespClient;
19
20use crate::{string, unexpected, vec2, vec3};
21
22const NUM_SLOTS: usize = 16384;
24
25pub struct ClusterClient {
28 shards: Vec<RespClient>,
30 slot_to_shard: Vec<u16>,
32}
33
34type Topology = (Vec<(String, u16)>, Vec<u16>);
36
37struct SlotRange {
39 start: u16,
40 end: u16,
41 host: String,
42 port: u16,
43}
44
45impl ClusterClient {
46 pub fn connect(host: &str, port: u16) -> io::Result<Self> {
49 let mut seed = RespClient::connect(host, port)?;
50 let reply = seed.request(&[b"CLUSTER".to_vec(), b"SLOTS".to_vec()])?;
51 let ranges = parse_cluster_slots(reply)?;
52 let (nodes, slot_to_shard) = build_topology(&ranges)?;
53 let shards = nodes
54 .iter()
55 .map(|(h, p)| RespClient::connect(h, *p))
56 .collect::<io::Result<Vec<_>>>()?;
57 Ok(Self { shards, slot_to_shard })
58 }
59
60 #[inline]
62 fn shard_for(&self, key: &[u8]) -> usize {
63 self.slot_to_shard[key_hash_slot(key) as usize] as usize
64 }
65
66 #[inline]
69 pub(crate) fn route_mut(&mut self, key: &[u8]) -> &mut RespClient {
70 let i = self.shard_for(key);
71 &mut self.shards[i]
72 }
73
74 pub fn request_keyed(&mut self, key: &[u8], args: &[Vec<u8>]) -> io::Result<Reply> {
76 let i = self.shard_for(key);
77 self.shards[i].request(args)
78 }
79
80 pub fn request_unkeyed(&mut self, args: &[Vec<u8>]) -> io::Result<Reply> {
83 self.shards[0].request(args)
84 }
85
86 pub fn shard_count(&self) -> usize {
88 self.shards.len()
89 }
90
91}
92
93impl ClusterClient {
100 pub fn ping(&mut self) -> io::Result<()> {
102 match self.request_unkeyed(&[b"PING".to_vec()])? {
103 Reply::Simple(s) if s == b"PONG" || s == b"OK" => Ok(()),
104 Reply::Error(e) => Err(io::Error::other(string(e))),
105 other => Err(unexpected(other)),
106 }
107 }
108
109 pub fn publish(&mut self, channel: &[u8], message: &[u8]) -> io::Result<usize> {
113 match self.request_unkeyed(&vec3(b"PUBLISH", channel, message))? {
114 Reply::Int(n) if n >= 0 => Ok(n as usize),
115 Reply::Error(e) => Err(io::Error::other(string(e))),
116 other => Err(unexpected(other)),
117 }
118 }
119
120 pub fn set(&mut self, key: &[u8], value: &[u8]) -> io::Result<()> {
122 match self.request_keyed(key, &vec3(b"SET", key, value))? {
123 Reply::Simple(s) if s == b"OK" => Ok(()),
124 Reply::Error(e) => Err(io::Error::other(string(e))),
125 other => Err(unexpected(other)),
126 }
127 }
128
129 pub fn set_with_ttl(&mut self, key: &[u8], value: &[u8], ttl: Duration) -> io::Result<()> {
131 let ms = ttl.as_millis().min(i64::MAX as u128) as i64;
132 let args = vec![
133 b"SET".to_vec(),
134 key.to_vec(),
135 value.to_vec(),
136 b"PX".to_vec(),
137 ms.to_string().into_bytes(),
138 ];
139 match self.request_keyed(key, &args)? {
140 Reply::Simple(s) if s == b"OK" => Ok(()),
141 Reply::Error(e) => Err(io::Error::other(string(e))),
142 other => Err(unexpected(other)),
143 }
144 }
145
146 pub fn get(&mut self, key: &[u8]) -> io::Result<Option<Vec<u8>>> {
148 match self.request_keyed(key, &vec2(b"GET", key))? {
149 Reply::Bulk(v) => Ok(Some(v)),
150 Reply::Nil => Ok(None),
151 Reply::Error(e) => Err(io::Error::other(string(e))),
152 other => Err(unexpected(other)),
153 }
154 }
155
156 pub fn incr(&mut self, key: &[u8]) -> io::Result<i64> {
158 match self.request_keyed(key, &vec2(b"INCR", key))? {
159 Reply::Int(n) => Ok(n),
160 Reply::Error(e) => Err(io::Error::other(string(e))),
161 other => Err(unexpected(other)),
162 }
163 }
164
165 pub fn incr_by(&mut self, key: &[u8], delta: i64) -> io::Result<i64> {
167 let args = vec![b"INCRBY".to_vec(), key.to_vec(), delta.to_string().into_bytes()];
168 match self.request_keyed(key, &args)? {
169 Reply::Int(n) => Ok(n),
170 Reply::Error(e) => Err(io::Error::other(string(e))),
171 other => Err(unexpected(other)),
172 }
173 }
174
175 pub fn expire(&mut self, key: &[u8], ttl: Duration) -> io::Result<bool> {
177 let ms = ttl.as_millis().min(i64::MAX as u128) as i64;
178 let args = vec![b"PEXPIRE".to_vec(), key.to_vec(), ms.to_string().into_bytes()];
179 match self.request_keyed(key, &args)? {
180 Reply::Int(1) => Ok(true),
181 Reply::Int(0) => Ok(false),
182 Reply::Error(e) => Err(io::Error::other(string(e))),
183 other => Err(unexpected(other)),
184 }
185 }
186
187 pub fn persist(&mut self, key: &[u8]) -> io::Result<bool> {
189 match self.request_keyed(key, &vec2(b"PERSIST", key))? {
190 Reply::Int(1) => Ok(true),
191 Reply::Int(0) => Ok(false),
192 Reply::Error(e) => Err(io::Error::other(string(e))),
193 other => Err(unexpected(other)),
194 }
195 }
196
197 pub fn ttl_ms(&mut self, key: &[u8]) -> io::Result<i64> {
199 match self.request_keyed(key, &vec2(b"PTTL", key))? {
200 Reply::Int(n) => Ok(n),
201 Reply::Error(e) => Err(io::Error::other(string(e))),
202 other => Err(unexpected(other)),
203 }
204 }
205
206 pub fn del(&mut self, keys: &[&[u8]]) -> io::Result<usize> {
209 let mut removed = 0;
210 for k in keys {
211 match self.request_keyed(k, &vec2(b"DEL", k))? {
212 Reply::Int(n) if n >= 0 => removed += n as usize,
213 Reply::Error(e) => return Err(io::Error::other(string(e))),
214 other => return Err(unexpected(other)),
215 }
216 }
217 Ok(removed)
218 }
219
220 pub fn exists(&mut self, keys: &[&[u8]]) -> io::Result<usize> {
223 let mut count = 0;
224 for k in keys {
225 match self.request_keyed(k, &vec2(b"EXISTS", k))? {
226 Reply::Int(n) if n >= 0 => count += n as usize,
227 Reply::Error(e) => return Err(io::Error::other(string(e))),
228 other => return Err(unexpected(other)),
229 }
230 }
231 Ok(count)
232 }
233
234 pub fn dbsize(&mut self) -> io::Result<usize> {
238 match self.request_unkeyed(&[b"DBSIZE".to_vec()])? {
239 Reply::Int(n) if n >= 0 => Ok(n as usize),
240 Reply::Error(e) => Err(io::Error::other(string(e))),
241 other => Err(unexpected(other)),
242 }
243 }
244
245 pub fn flushall(&mut self) -> io::Result<()> {
248 match self.request_unkeyed(&[b"FLUSHALL".to_vec()])? {
249 Reply::Simple(s) if s == b"OK" => Ok(()),
250 Reply::Error(e) => Err(io::Error::other(string(e))),
251 other => Err(unexpected(other)),
252 }
253 }
254}
255
256fn build_topology(ranges: &[SlotRange]) -> io::Result<Topology> {
261 if ranges.is_empty() {
262 return Err(io::Error::new(
263 io::ErrorKind::InvalidData,
264 "CLUSTER SLOTS returned no ranges",
265 ));
266 }
267 let mut nodes: Vec<(String, u16)> = Vec::new();
268 let mut slot_to_shard = vec![0u16; NUM_SLOTS];
269 for r in ranges {
270 let idx = if let Some(i) = nodes.iter().position(|(h, p)| h == &r.host && *p == r.port) { i } else {
271 nodes.push((r.host.clone(), r.port));
272 nodes.len() - 1
273 } as u16;
274 for slot in r.start..=r.end {
275 slot_to_shard[slot as usize] = idx;
276 }
277 }
278 Ok((nodes, slot_to_shard))
279}
280
281fn parse_cluster_slots(reply: Reply) -> io::Result<Vec<SlotRange>> {
283 fn bad() -> io::Error {
284 io::Error::new(io::ErrorKind::InvalidData, "malformed CLUSTER SLOTS reply")
285 }
286 let Reply::Array(rows) = reply else {
287 return Err(bad());
288 };
289 let mut out = Vec::with_capacity(rows.len());
290 for row in rows {
291 let Reply::Array(cols) = row else { return Err(bad()) };
292 if cols.len() < 3 {
293 return Err(bad());
294 }
295 let start = as_int(&cols[0]).ok_or_else(bad)?;
296 let end = as_int(&cols[1]).ok_or_else(bad)?;
297 let Reply::Array(node) = &cols[2] else {
298 return Err(bad());
299 };
300 if node.len() < 2 {
301 return Err(bad());
302 }
303 let host = as_str(&node[0]).ok_or_else(bad)?;
304 let port = as_int(&node[1]).ok_or_else(bad)?;
305 if !(0..=i64::from(u16::MAX)).contains(&start)
306 || !(0..=i64::from(u16::MAX)).contains(&end)
307 || !(0..=i64::from(u16::MAX)).contains(&port)
308 {
309 return Err(bad());
310 }
311 out.push(SlotRange {
312 start: start as u16,
313 end: end as u16,
314 host,
315 port: port as u16,
316 });
317 }
318 Ok(out)
319}
320
321fn as_int(r: &Reply) -> Option<i64> {
322 match r {
323 Reply::Int(n) => Some(*n),
324 _ => None,
325 }
326}
327
328fn as_str(r: &Reply) -> Option<String> {
329 match r {
330 Reply::Bulk(b) | Reply::Simple(b) => Some(String::from_utf8_lossy(b).into_owned()),
331 _ => None,
332 }
333}
334
335#[cfg(test)]
336mod tests {
337 use super::*;
338
339 fn four_shard_reply() -> Reply {
342 let row = |start: i64, end: i64, port: i64| {
344 Reply::Array(vec![
345 Reply::Int(start),
346 Reply::Int(end),
347 Reply::Array(vec![
348 Reply::Bulk(b"127.0.0.1".to_vec()),
349 Reply::Int(port),
350 Reply::Bulk(b"nodeid".to_vec()),
351 Reply::Array(vec![]),
352 ]),
353 ])
354 };
355 Reply::Array(vec![
356 row(0, 4095, 7001),
357 row(4096, 8191, 7002),
358 row(8192, 12287, 7003),
359 row(12288, 16383, 7004),
360 ])
361 }
362
363 #[test]
364 fn parse_and_build_4_shard_topology() {
365 let ranges = parse_cluster_slots(four_shard_reply()).unwrap();
366 assert_eq!(ranges.len(), 4);
367 let (nodes, slot_to_shard) = build_topology(&ranges).unwrap();
368 assert_eq!(nodes.len(), 4);
369 assert_eq!(nodes[0], ("127.0.0.1".to_string(), 7001));
370 assert_eq!(nodes[3], ("127.0.0.1".to_string(), 7004));
371 assert_eq!(slot_to_shard[0], 0);
373 assert_eq!(slot_to_shard[4095], 0);
374 assert_eq!(slot_to_shard[4096], 1);
375 assert_eq!(slot_to_shard[8192], 2);
376 assert_eq!(slot_to_shard[16383], 3);
377 }
378
379 #[test]
380 fn keys_route_to_their_slot_owner() {
381 let ranges = parse_cluster_slots(four_shard_reply()).unwrap();
382 let (_, slot_to_shard) = build_topology(&ranges).unwrap();
383 for k in ["k0", "k1", "user:42", "rate:10.0.0.1", "gl:abc"] {
386 let slot = key_hash_slot(k.as_bytes()) as usize;
387 let shard = slot_to_shard[slot] as usize;
388 assert_eq!(shard, slot / 4096, "key {k} slot {slot}");
390 }
391 }
392
393 #[test]
394 fn rejects_empty_and_malformed() {
395 assert!(parse_cluster_slots(Reply::Int(1)).is_err());
396 assert!(build_topology(&[]).is_err());
397 assert!(parse_cluster_slots(Reply::Array(vec![Reply::Array(vec![Reply::Int(0)])])).is_err());
398 }
399}