1use core::fmt::Write as _;
49use std::sync::atomic::Ordering::Relaxed;
50use std::sync::atomic::{AtomicBool, AtomicU64};
51
52use yo_common::lock::Lock;
53use yo_common::{Code, Error, Result};
54
55use crate::reply::Out;
56
57use super::args::{self, Args};
58use super::{Server, Session};
59
60mod bus;
61use super::keyspec;
62use super::table::Spec;
63
64pub const SLOTS: usize = 16384;
70
71const WRITABLE_DELAY_MS: u64 = 2000;
78
79const REJOIN_DELAY_MS: u64 = 5000;
89
90const BUS_OFFSET: u16 = 10000;
92
93const ID_LEN: usize = 40;
95
96#[rustfmt::skip]
106const CRC16: [u16; 256] = [
107 0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50a5, 0x60c6, 0x70e7,
108 0x8108, 0x9129, 0xa14a, 0xb16b, 0xc18c, 0xd1ad, 0xe1ce, 0xf1ef,
109 0x1231, 0x0210, 0x3273, 0x2252, 0x52b5, 0x4294, 0x72f7, 0x62d6,
110 0x9339, 0x8318, 0xb37b, 0xa35a, 0xd3bd, 0xc39c, 0xf3ff, 0xe3de,
111 0x2462, 0x3443, 0x0420, 0x1401, 0x64e6, 0x74c7, 0x44a4, 0x5485,
112 0xa56a, 0xb54b, 0x8528, 0x9509, 0xe5ee, 0xf5cf, 0xc5ac, 0xd58d,
113 0x3653, 0x2672, 0x1611, 0x0630, 0x76d7, 0x66f6, 0x5695, 0x46b4,
114 0xb75b, 0xa77a, 0x9719, 0x8738, 0xf7df, 0xe7fe, 0xd79d, 0xc7bc,
115 0x48c4, 0x58e5, 0x6886, 0x78a7, 0x0840, 0x1861, 0x2802, 0x3823,
116 0xc9cc, 0xd9ed, 0xe98e, 0xf9af, 0x8948, 0x9969, 0xa90a, 0xb92b,
117 0x5af5, 0x4ad4, 0x7ab7, 0x6a96, 0x1a71, 0x0a50, 0x3a33, 0x2a12,
118 0xdbfd, 0xcbdc, 0xfbbf, 0xeb9e, 0x9b79, 0x8b58, 0xbb3b, 0xab1a,
119 0x6ca6, 0x7c87, 0x4ce4, 0x5cc5, 0x2c22, 0x3c03, 0x0c60, 0x1c41,
120 0xedae, 0xfd8f, 0xcdec, 0xddcd, 0xad2a, 0xbd0b, 0x8d68, 0x9d49,
121 0x7e97, 0x6eb6, 0x5ed5, 0x4ef4, 0x3e13, 0x2e32, 0x1e51, 0x0e70,
122 0xff9f, 0xefbe, 0xdfdd, 0xcffc, 0xbf1b, 0xaf3a, 0x9f59, 0x8f78,
123 0x9188, 0x81a9, 0xb1ca, 0xa1eb, 0xd10c, 0xc12d, 0xf14e, 0xe16f,
124 0x1080, 0x00a1, 0x30c2, 0x20e3, 0x5004, 0x4025, 0x7046, 0x6067,
125 0x83b9, 0x9398, 0xa3fb, 0xb3da, 0xc33d, 0xd31c, 0xe37f, 0xf35e,
126 0x02b1, 0x1290, 0x22f3, 0x32d2, 0x4235, 0x5214, 0x6277, 0x7256,
127 0xb5ea, 0xa5cb, 0x95a8, 0x8589, 0xf56e, 0xe54f, 0xd52c, 0xc50d,
128 0x34e2, 0x24c3, 0x14a0, 0x0481, 0x7466, 0x6447, 0x5424, 0x4405,
129 0xa7db, 0xb7fa, 0x8799, 0x97b8, 0xe75f, 0xf77e, 0xc71d, 0xd73c,
130 0x26d3, 0x36f2, 0x0691, 0x16b0, 0x6657, 0x7676, 0x4615, 0x5634,
131 0xd94c, 0xc96d, 0xf90e, 0xe92f, 0x99c8, 0x89e9, 0xb98a, 0xa9ab,
132 0x5844, 0x4865, 0x7806, 0x6827, 0x18c0, 0x08e1, 0x3882, 0x28a3,
133 0xcb7d, 0xdb5c, 0xeb3f, 0xfb1e, 0x8bf9, 0x9bd8, 0xabbb, 0xbb9a,
134 0x4a75, 0x5a54, 0x6a37, 0x7a16, 0x0af1, 0x1ad0, 0x2ab3, 0x3a92,
135 0xfd2e, 0xed0f, 0xdd6c, 0xcd4d, 0xbdaa, 0xad8b, 0x9de8, 0x8dc9,
136 0x7c26, 0x6c07, 0x5c64, 0x4c45, 0x3ca2, 0x2c83, 0x1ce0, 0x0cc1,
137 0xef1f, 0xff3e, 0xcf5d, 0xdf7c, 0xaf9b, 0xbfba, 0x8fd9, 0x9ff8,
138 0x6e17, 0x7e36, 0x4e55, 0x5e74, 0x2e93, 0x3eb2, 0x0ed1, 0x1ef0,
139];
140
141#[must_use]
143fn crc16(data: &[u8]) -> u16 {
144 let mut crc: u16 = 0;
145 for &byte in data {
146 let at = ((crc >> 8) ^ u16::from(byte)) & 0xff;
147 crc = (crc << 8) ^ CRC16[at as usize];
148 }
149 crc
150}
151
152#[must_use]
162pub fn key_slot(key: &[u8]) -> u16 {
163 let tagged = match key.iter().position(|&b| b == b'{') {
164 Some(open) => match key[open + 1..].iter().position(|&b| b == b'}') {
165 Some(0) | None => key,
167 Some(len) => &key[open + 1..open + 1 + len],
168 },
169 None => key,
170 };
171 crc16(tagged) % SLOTS as u16
172}
173
174pub(crate) const FLAG_MASTER: u16 = 1;
182pub(crate) const FLAG_SLAVE: u16 = 2;
183pub(crate) const FLAG_PFAIL: u16 = 4;
184pub(crate) const FLAG_FAIL: u16 = 8;
185pub(crate) const FLAG_MYSELF: u16 = 16;
186pub(crate) const FLAG_HANDSHAKE: u16 = 32;
187pub(crate) const FLAG_NOADDR: u16 = 64;
188pub(crate) const FLAG_MEET: u16 = 128;
189pub(crate) const FLAG_MIGRATE_TO: u16 = 256;
190pub(crate) const FLAG_NOFAILOVER: u16 = 512;
191pub(crate) const FLAG_EXTENSIONS: u16 = 1024;
192
193#[derive(Clone)]
195struct Node {
196 id: String,
198 host: String,
201 port: u16,
203 bus: u16,
207 shard: String,
209 epoch: u64,
211 flags: u16,
213 master: Option<u16>,
215 ping_sent: u64,
218 pong_recv: u64,
220 data_recv: u64,
224 fail_time: u64,
226 offset: u64,
228 linked: bool,
230 reports: Vec<(String, u64)>,
233}
234
235impl Node {
236 fn new(id: String, host: String, port: u16, bus: u16, flags: u16, now: u64) -> Node {
238 Node {
239 id,
240 host,
241 port,
242 bus,
243 shard: String::from_utf8_lossy(&new_id()).into_owned(),
244 epoch: 0,
245 flags,
246 master: None,
247 ping_sent: 0,
248 pong_recv: 0,
253 data_recv: now,
256 fail_time: 0,
257 offset: 0,
258 linked: false,
259 reports: Vec::new(),
260 }
261 }
262
263 fn is_master(&self) -> bool {
266 self.flags & FLAG_SLAVE == 0
267 }
268
269 fn down(&self) -> bool {
271 self.flags & (FLAG_PFAIL | FLAG_FAIL) != 0
272 }
273
274 fn flag_names(&self, into: &mut String) {
277 const NAMES: [(u16, &str); 8] = [
278 (FLAG_MYSELF, "myself"),
279 (FLAG_MASTER, "master"),
280 (FLAG_SLAVE, "slave"),
281 (FLAG_PFAIL, "fail?"),
282 (FLAG_FAIL, "fail"),
283 (FLAG_HANDSHAKE, "handshake"),
284 (FLAG_NOADDR, "noaddr"),
285 (FLAG_NOFAILOVER, "nofailover"),
286 ];
287 let mut first = true;
288 for (bit, name) in NAMES {
289 if self.flags & bit == 0 {
290 continue;
291 }
292 if !first {
293 into.push(',');
294 }
295 into.push_str(name);
296 first = false;
297 }
298 if first {
299 into.push_str("noflags");
300 }
301 }
302
303 fn address(&self, into: &mut String) {
305 let _ = write!(into, "{}:{}@{}", self.host, self.port, self.bus);
306 }
307
308 fn address_on_disk(&self, into: &mut String) {
313 self.address(into);
314 let _ = write!(into, ",,tls-port=0,shard-id={}", self.shard);
315 }
316}
317
318struct Map {
325 nodes: Vec<Node>,
327 owner: Vec<Option<u16>>,
329 migrating: Vec<Option<u16>>,
331 importing: Vec<Option<u16>>,
333}
334
335impl Map {
336 fn new(me: Node) -> Map {
338 Map {
339 nodes: vec![me],
340 owner: vec![None; SLOTS],
341 migrating: vec![None; SLOTS],
342 importing: vec![None; SLOTS],
343 }
344 }
345
346 fn find(&self, id: &[u8]) -> Option<u16> {
348 self.nodes
349 .iter()
350 .position(|n| n.id.as_bytes() == id)
351 .map(|at| at as u16)
352 }
353
354 fn forget(&mut self, at: u16) {
363 self.nodes.remove(usize::from(at));
364 let shift = |slot: &mut Option<u16>| match *slot {
365 Some(node) if node == at => *slot = None,
366 Some(node) if node > at => *slot = Some(node - 1),
367 _ => {}
368 };
369 for slot in 0..SLOTS {
370 shift(&mut self.owner[slot]);
371 shift(&mut self.migrating[slot]);
372 shift(&mut self.importing[slot]);
373 }
374 for node in &mut self.nodes {
375 shift(&mut node.master);
376 }
377 }
378
379 fn voters(&self) -> usize {
382 let mut seen = vec![false; self.nodes.len()];
383 for owner in self.owner.iter().flatten() {
384 seen[*owner as usize] = true;
385 }
386 seen.iter().filter(|s| **s).count()
387 }
388
389 fn mine(&self, slot: u16) -> bool {
391 self.owner[slot as usize] == Some(0)
392 }
393
394 fn assigned(&self) -> usize {
396 self.owner.iter().filter(|o| o.is_some()).count()
397 }
398
399 fn size(&self) -> usize {
402 let mut seen = vec![false; self.nodes.len()];
403 for owner in self.owner.iter().flatten() {
404 seen[*owner as usize] = true;
405 }
406 seen.iter().filter(|s| **s).count()
407 }
408
409 fn runs(&self, node: u16) -> Vec<(u16, u16)> {
411 let mut runs: Vec<(u16, u16)> = Vec::new();
412 for slot in 0..SLOTS as u16 {
413 if self.owner[slot as usize] != Some(node) {
414 continue;
415 }
416 match runs.last_mut() {
417 Some(last) if last.1 + 1 == slot => last.1 = slot,
418 _ => runs.push((slot, slot)),
419 }
420 }
421 runs
422 }
423}
424
425pub(crate) struct Cluster {
430 on: bool,
435 map: Lock<Map>,
437 epoch: AtomicU64,
439 covered_at: AtomicU64,
442 booted_at: AtomicU64,
445 was_down: AtomicBool,
448 full_coverage: AtomicBool,
451 reads_when_down: AtomicBool,
460 file: Lock<String>,
462 bus: bus::Bus,
465}
466
467impl Default for Cluster {
468 fn default() -> Cluster {
469 Cluster {
470 on: false,
471 map: Lock::new(Map {
472 nodes: Vec::new(),
473 owner: Vec::new(),
474 migrating: Vec::new(),
475 importing: Vec::new(),
476 }),
477 epoch: AtomicU64::new(0),
478 covered_at: AtomicU64::new(0),
479 booted_at: AtomicU64::new(0),
480 was_down: AtomicBool::new(false),
481 full_coverage: AtomicBool::new(true),
482 reads_when_down: AtomicBool::new(false),
483 file: Lock::new(String::new()),
484 bus: bus::Bus::default(),
485 }
486 }
487}
488
489impl Server {
490 #[must_use]
493 pub fn cluster_enabled(&self) -> bool {
494 self.cluster.on
495 }
496
497 pub fn enable_cluster(&mut self, file: &str, port: u16) {
503 self.cluster.on = true;
504 self.cluster.booted_at.store(self.now_ms(), Relaxed);
505 let now = self.now_ms();
506 let me = yo_alloc::allow(|| {
507 Node::new(
508 String::from_utf8_lossy(&new_id()).into_owned(),
509 String::new(),
510 port,
511 port + BUS_OFFSET,
512 FLAG_MYSELF | FLAG_MASTER,
513 now,
514 )
515 });
516 let path = yo_alloc::allow(|| {
522 if file.is_empty() {
523 String::new()
524 } else {
525 self.dir().join(file).to_string_lossy().into_owned()
526 }
527 });
528 yo_alloc::allow(|| {
529 *self.cluster.map.lock() = Map::new(me);
530 *self.cluster.file.lock() = path;
531 *self.cluster.bus.secret.lock() = String::from_utf8_lossy(&new_id()).into_owned();
537 });
538 if let Err(e) = self.reload_cluster() {
543 eprintln!("cluster config file could not be read: {e}");
544 }
545 self.recount_coverage();
546 }
547
548 pub(crate) fn cluster_secret(&self) -> String {
558 let held = self.cluster.bus.secret.lock();
559 yo_alloc::allow(|| held.clone())
560 }
561
562 pub(crate) fn cluster_full_coverage(&self) -> bool {
564 self.cluster.full_coverage.load(Relaxed)
565 }
566
567 pub(crate) fn cluster_reads_when_down(&self) -> bool {
569 self.cluster.reads_when_down.load(Relaxed)
570 }
571
572 pub(crate) fn set_cluster_coverage(&self, full: bool, reads_when_down: bool) {
575 self.cluster.full_coverage.store(full, Relaxed);
576 self.cluster.reads_when_down.store(reads_when_down, Relaxed);
577 self.recount_coverage();
578 }
579
580 pub(crate) fn cluster_file(&self) -> String {
582 let file = self.cluster.file.lock();
583 yo_alloc::allow(|| file.clone())
584 }
585
586 pub(crate) fn cluster_id(&self) -> String {
588 let map = self.cluster.map.lock();
589 yo_alloc::allow(|| map.nodes.first().map_or_else(String::new, |n| n.id.clone()))
590 }
591
592 pub(crate) fn cluster_up(&self) -> bool {
599 let at = self.cluster.covered_at.load(Relaxed);
600 if at == 0 {
601 return false;
602 }
603 let (since, wait) = if self.cluster.was_down.load(Relaxed) {
604 (at, REJOIN_DELAY_MS)
605 } else {
606 (self.cluster.booted_at.load(Relaxed), WRITABLE_DELAY_MS)
607 };
608 self.now_ms().saturating_sub(since) >= wait
609 }
610
611 fn recount_coverage(&self) {
617 let covered = {
618 let map = self.cluster.map.lock();
619 let assigned = map.assigned();
620 if self.cluster_full_coverage() {
621 assigned == SLOTS
622 } else {
623 assigned > 0
624 }
625 };
626 if covered {
627 let _ =
628 self.cluster
629 .covered_at
630 .compare_exchange(0, self.now_ms().max(1), Relaxed, Relaxed);
631 } else {
632 self.cluster.covered_at.store(0, Relaxed);
633 self.cluster.was_down.store(true, Relaxed);
634 }
635 }
636}
637
638fn new_id() -> [u8; ID_LEN] {
640 const HEX: &[u8; 16] = b"0123456789abcdef";
641 let mut raw = [0u8; ID_LEN / 2];
642 yo_common::entropy::fill(&mut raw);
643 let mut id = [0u8; ID_LEN];
644 for (i, byte) in raw.iter().enumerate() {
645 id[i * 2] = HEX[usize::from(byte >> 4)];
646 id[i * 2 + 1] = HEX[usize::from(byte & 15)];
647 }
648 id
649}
650
651pub(super) fn asks(spec: &Spec) -> bool {
661 spec.flags.contains(&"asking")
662}
663
664pub(super) fn gate(
683 server: &Server,
684 db: usize,
685 asking: bool,
686 spec: &Spec,
687 args: Args<'_>,
688) -> Option<Error> {
689 if !keyspec::takes_keys(spec, args, 0) {
692 return None;
693 }
694 let mut slot: Option<u16> = None;
695 let mut crossed = false;
696 let mut keys = 0usize;
697 let mut present = 0usize;
698 let mut missing = 0usize;
699 let (owner, migrating, importing, here) = {
700 let map = server.cluster.map.lock();
701 keyspec::find(spec, args, 0, &mut |run| {
703 for i in 0..run.count {
704 let at = run.first + i * run.step;
705 if at >= args.len() {
706 continue;
707 }
708 let this = key_slot(args.get(at));
709 keys += 1;
710 match slot {
711 None => slot = Some(this),
712 Some(first) if first != this => crossed = true,
713 Some(_) => {}
714 }
715 }
716 });
717 let at = usize::from(slot?);
718 (
719 map.owner[at],
720 map.migrating[at].map(|to| node_at(&map, to)),
721 map.importing[at].is_some(),
722 map.owner[at] == Some(0),
723 )
724 };
725 let slot = slot?;
726 let Some(owner) = owner else {
729 return Some(Error::new(
730 Code::Invalid,
731 "CLUSTERDOWN Hash slot not served",
732 ));
733 };
734 if crossed {
735 return Some(Error::new(
736 Code::Invalid,
737 "CROSSSLOT Keys in request don't hash to the same slot",
738 ));
739 }
740 if !server.cluster_up() {
745 if !server.cluster.reads_when_down.load(Relaxed) {
746 return Some(Error::new(Code::Invalid, "CLUSTERDOWN The cluster is down"));
747 }
748 if spec.flags.contains(&"write") {
749 return Some(Error::new(
750 Code::Invalid,
751 "CLUSTERDOWN The cluster is down and only accepts read commands",
752 ));
753 }
754 }
755 if migrating.is_some() || importing {
759 let held = &server.dbs[db];
760 keyspec::find(spec, args, 0, &mut |run| {
761 for i in 0..run.count {
762 let at = run.first + i * run.step;
763 if at >= args.len() {
764 continue;
765 }
766 let key = args.get(at);
767 let mut stripe = held.hold(key);
768 if stripe.exists(key) {
769 present += 1;
770 } else {
771 missing += 1;
772 }
773 }
774 });
775 }
776 if let Some(to) = migrating
777 && missing > 0
778 {
779 if present > 0 {
782 return Some(Error::new(
783 Code::Invalid,
784 "TRYAGAIN Multiple keys request during rehashing of slot",
785 ));
786 }
787 return Some(redirect("ASK", slot, &to));
788 }
789 if importing && asking {
790 if keys > 1 && missing > 0 {
791 return Some(Error::new(
792 Code::Invalid,
793 "TRYAGAIN Multiple keys request during rehashing of slot",
794 ));
795 }
796 return None;
797 }
798 if here {
799 return None;
800 }
801 let (host, port) = {
802 let map = server.cluster.map.lock();
803 node_at(&map, owner)
804 };
805 Some(redirect("MOVED", slot, &(host, port)))
806}
807
808fn node_at(map: &Map, at: u16) -> (String, u16) {
810 let node = &map.nodes[at as usize];
811 (yo_alloc::allow(|| node.host.clone()), node.port)
812}
813
814fn redirect(word: &str, slot: u16, node: &(String, u16)) -> Error {
820 let host = if node.0.is_empty() {
821 "127.0.0.1"
822 } else {
823 node.0.as_str()
824 };
825 Error::fmt(
826 Code::Invalid,
827 format_args!("{word} {slot} {host}:{}", node.1),
828 )
829}
830
831pub(super) fn execute(
835 server: &Server,
836 session: &mut Session,
837 args: Args<'_>,
838 out: &mut Out,
839) -> Result<()> {
840 let session_db = session.db;
841 let sub = args.get(1);
842 if !server.cluster_enabled() {
846 return match arity_of(sub) {
847 Some(n) if !arity_ok(n, args.len()) => Err(wrong_sub_arity(sub)),
848 Some(_) => Err(disabled()),
849 None => Err(args::unknown_subcommand(sub, "CLUSTER")),
850 };
851 }
852 let Some(n) = arity_of(sub) else {
853 return Err(args::unknown_subcommand(sub, "CLUSTER"));
854 };
855 if !arity_ok(n, args.len()) {
856 return Err(wrong_sub_arity(sub));
857 }
858 match () {
859 () if args::is(sub, b"myid") => out.bulk(server.cluster_id().as_bytes()),
860 () if args::is(sub, b"myshardid") => {
861 let map = server.cluster.map.lock();
862 out.bulk(map.nodes[0].shard.as_bytes());
863 }
864 () if args::is(sub, b"keyslot") => out.int(i64::from(key_slot(args.get(2)))),
865 () if args::is(sub, b"info") => info(server, out),
866 () if args::is(sub, b"nodes") => nodes(server, out),
867 () if args::is(sub, b"slots") => reply_slots(server, out),
868 () if args::is(sub, b"shards") => shards(server, out),
869 () if args::is(sub, b"links") => server.cluster_links(out),
870 () if args::is(sub, b"slaves") || args::is(sub, b"replicas") => {
871 replicas(server, args.get(2), out)?;
872 }
873 () if args::is(sub, b"count-failure-reports") => {
874 let map = server.cluster.map.lock();
875 let Some(at) = map.find(args.get(2)) else {
876 return Err(unknown_node(args.get(2)));
877 };
878 out.int(map.nodes[usize::from(at)].reports.len() as i64);
879 }
880 () if args::is(sub, b"countkeysinslot") => count_keys(server, session_db, args, out)?,
881 () if args::is(sub, b"getkeysinslot") => get_keys(server, session_db, args, out)?,
882 () if args::is(sub, b"addslots") => {
883 add_or_del(server, args, true, false)?;
884 out.ok();
885 }
886 () if args::is(sub, b"delslots") => {
887 add_or_del(server, args, false, false)?;
888 out.ok();
889 }
890 () if args::is(sub, b"addslotsrange") => {
891 add_or_del(server, args, true, true)?;
892 out.ok();
893 }
894 () if args::is(sub, b"delslotsrange") => {
895 add_or_del(server, args, false, true)?;
896 out.ok();
897 }
898 () if args::is(sub, b"setslot") => setslot(server, session_db, args, out)?,
899 () if args::is(sub, b"flushslots") => flushslots(server, out)?,
900 () if args::is(sub, b"bumpepoch") => bumpepoch(server, out)?,
901 () if args::is(sub, b"set-config-epoch") => set_config_epoch(server, args, out)?,
902 () if args::is(sub, b"reset") => {
903 if args.len() > 3 {
904 return Err(sub_syntax(sub));
905 }
906 reset(server, args, out)?;
907 }
908 () if args::is(sub, b"slot-stats") => slot_stats(server, session_db, args, out)?,
909 () if args::is(sub, b"migration") => migration(server, args, out)?,
910 () if args::is(sub, b"syncslots") => syncslots(server, session, args, out)?,
911 () if args::is(sub, b"saveconfig") => {
912 save(server)?;
913 out.ok();
914 }
915 () if args::is(sub, b"forget") => {
916 forget(server, args.get(2))?;
917 out.ok();
918 }
919 () if args::is(sub, b"replicate") => {
920 replicate(server, session_db, args.get(2))?;
921 out.ok();
922 }
923 () if args::is(sub, b"failover") => {
924 if args.len() > 3 {
925 return Err(sub_syntax(sub));
926 }
927 if args.len() == 3
928 && !args::is(args.get(2), b"force")
929 && !args::is(args.get(2), b"takeover")
930 {
931 return Err(args::syntax());
932 }
933 return Err(Error::new(
934 Code::Invalid,
935 "You should send CLUSTER FAILOVER to a replica",
936 ));
937 }
938 () if args::is(sub, b"meet") => {
939 if args.len() > 5 {
940 return Err(sub_syntax(sub));
941 }
942 let (host, port, bus) = meet(&args)?;
943 server.cluster_meet(&host, port, bus);
944 out.ok();
945 }
946 () if args::is(sub, b"help") => help(out),
947 _ => return Err(args::unknown_subcommand(sub, "CLUSTER")),
948 }
949 Ok(())
950}
951
952pub(super) fn disabled() -> Error {
954 Error::new(Code::Invalid, "This instance has cluster support disabled")
955}
956
957fn not_yet(what: &str) -> Error {
963 Error::fmt(
964 Code::Invalid,
965 format_args!("{what} is not implemented yet, move the slot with SETSLOT and MIGRATE"),
966 )
967}
968
969fn unknown_node(id: &[u8]) -> Error {
971 Error::fmt(
972 Code::Invalid,
973 format_args!("Unknown node {}", String::from_utf8_lossy(id)),
974 )
975}
976
977fn dont_know(id: &[u8]) -> Error {
980 Error::fmt(
981 Code::Invalid,
982 format_args!("I don't know about node {}", String::from_utf8_lossy(id)),
983 )
984}
985
986fn arity_of(sub: &[u8]) -> Option<i32> {
993 const TABLE: &[(&str, i32)] = &[
994 ("addslots", -3),
995 ("addslotsrange", -4),
996 ("bumpepoch", 2),
997 ("count-failure-reports", 3),
998 ("countkeysinslot", 3),
999 ("delslots", -3),
1000 ("delslotsrange", -4),
1001 ("failover", -2),
1002 ("flushslots", 2),
1003 ("forget", 3),
1004 ("getkeysinslot", 4),
1005 ("help", 2),
1006 ("info", 2),
1007 ("keyslot", 3),
1008 ("links", 2),
1009 ("meet", -4),
1010 ("migration", -4),
1011 ("myid", 2),
1012 ("myshardid", 2),
1013 ("nodes", 2),
1014 ("replicas", 3),
1015 ("replicate", 3),
1016 ("reset", -2),
1017 ("saveconfig", 2),
1018 ("set-config-epoch", 3),
1019 ("setslot", -4),
1020 ("shards", 2),
1021 ("slaves", 3),
1022 ("slot-stats", -4),
1023 ("slots", 2),
1024 ("syncslots", -3),
1025 ];
1026 TABLE
1027 .iter()
1028 .find(|(name, _)| args::is(sub, name.as_bytes()))
1029 .map(|(_, arity)| *arity)
1030}
1031
1032fn arity_ok(arity: i32, len: usize) -> bool {
1034 let len = len as i32;
1035 if arity >= 0 {
1036 len == arity
1037 } else {
1038 len >= -arity
1039 }
1040}
1041
1042fn sub_syntax(sub: &[u8]) -> Error {
1050 Error::fmt(
1051 Code::Unsupported,
1052 format_args!(
1053 "unknown subcommand or wrong number of arguments for '{}'. Try CLUSTER HELP.",
1054 String::from_utf8_lossy(sub)
1055 ),
1056 )
1057}
1058
1059fn wrong_sub_arity(sub: &[u8]) -> Error {
1061 Error::fmt(
1062 Code::Invalid,
1063 format_args!(
1064 "wrong number of arguments for 'cluster|{}' command",
1065 String::from_utf8_lossy(sub).to_lowercase()
1066 ),
1067 )
1068}
1069
1070fn info(server: &Server, out: &mut Out) {
1075 let (assigned, size, known, my_epoch) = {
1076 let map = server.cluster.map.lock();
1077 (
1078 map.assigned(),
1079 map.size(),
1080 map.nodes.len(),
1081 map.nodes[0].epoch,
1082 )
1083 };
1084 let state = if server.cluster_up() { "ok" } else { "fail" };
1085 let text = yo_alloc::allow(|| {
1086 let mut s = String::with_capacity(512);
1087 let _ = write!(
1088 s,
1089 "cluster_state:{state}\r\ncluster_slots_assigned:{assigned}\r\n\
1090 cluster_slots_ok:{assigned}\r\ncluster_slots_pfail:0\r\ncluster_slots_fail:0\r\n\
1091 cluster_known_nodes:{known}\r\ncluster_size:{size}\r\n\
1092 cluster_current_epoch:{}\r\ncluster_my_epoch:{my_epoch}\r\n\
1093 cluster_stats_messages_sent:0\r\ncluster_stats_messages_received:0\r\n\
1094 total_cluster_links_buffer_limit_exceeded:0\r\n\
1095 cluster_slot_migration_active_tasks:0\r\n\
1096 cluster_slot_migration_active_trim_running:0\r\n\
1097 cluster_slot_migration_active_trim_current_job_keys:0\r\n\
1098 cluster_slot_migration_active_trim_current_job_trimmed:0\r\n\
1099 cluster_slot_migration_stats_active_trim_started:0\r\n\
1100 cluster_slot_migration_stats_active_trim_completed:0\r\n\
1101 cluster_slot_migration_stats_active_trim_cancelled:0\r\n",
1102 server.cluster.epoch.load(Relaxed),
1103 );
1104 s
1105 });
1106 out.verbatim(b"txt", text.as_bytes());
1107}
1108
1109fn nodes(server: &Server, out: &mut Out) {
1111 let text = yo_alloc::allow(|| lines(server, false));
1112 out.verbatim(b"txt", text.as_bytes());
1113}
1114
1115fn lines(server: &Server, on_disk: bool) -> String {
1121 let map = server.cluster.map.lock();
1122 let mut s = String::with_capacity(256);
1123 for at in 0..map.nodes.len() as u16 {
1124 describe(&map, at, on_disk, &mut s);
1125 s.push('\n');
1126 }
1127 s
1128}
1129
1130fn describe(map: &Map, at: u16, on_disk: bool, s: &mut String) {
1133 let node = &map.nodes[usize::from(at)];
1134 s.push_str(&node.id);
1135 s.push(' ');
1136 if on_disk {
1137 node.address_on_disk(s);
1138 } else {
1139 node.address(s);
1140 }
1141 s.push(' ');
1142 node.flag_names(s);
1143 s.push(' ');
1144 match node.master.and_then(|m| map.nodes.get(usize::from(m))) {
1145 Some(master) => s.push_str(&master.id),
1146 None => s.push('-'),
1147 }
1148 let epoch = match node.master.and_then(|m| map.nodes.get(usize::from(m))) {
1151 Some(master) => master.epoch,
1152 None => node.epoch,
1153 };
1154 let link = if node.linked || at == 0 {
1155 "connected"
1156 } else {
1157 "disconnected"
1158 };
1159 let _ = write!(s, " {} {} {epoch} {link}", node.ping_sent, node.pong_recv);
1160 for (from, to) in map.runs(at) {
1161 if from == to {
1162 let _ = write!(s, " {from}");
1163 } else {
1164 let _ = write!(s, " {from}-{to}");
1165 }
1166 }
1167 if at == 0 {
1168 for slot in 0..SLOTS {
1169 if let Some(to) = map.migrating[slot] {
1170 let _ = write!(s, " [{slot}->-{}]", map.nodes[to as usize].id);
1171 }
1172 if let Some(from) = map.importing[slot] {
1173 let _ = write!(s, " [{slot}-<-{}]", map.nodes[from as usize].id);
1174 }
1175 }
1176 }
1177}
1178
1179fn reply_slots(server: &Server, out: &mut Out) {
1185 let mine = server.repl_offset();
1186 let map = server.cluster.map.lock();
1187 let at = out.len();
1188 let mut n = 0;
1189 let mut run: Option<(u16, u16)> = None;
1190 for slot in 0..=SLOTS as u16 {
1191 let owner = if slot as usize == SLOTS {
1192 None
1193 } else {
1194 map.owner[slot as usize]
1195 };
1196 match run {
1197 Some((node, _)) if owner == Some(node) => {}
1198 Some((node, from)) => {
1199 slot_run(&map, node, from, slot - 1, mine, out);
1200 n += 1;
1201 run = owner.map(|node| (node, slot));
1202 }
1203 None => run = owner.map(|node| (node, slot)),
1204 }
1205 }
1206 out.close_array(at, n);
1207}
1208
1209fn slot_run(map: &Map, node: u16, from: u16, to: u16, mine: u64, out: &mut Out) {
1217 let replicas: Vec<&Node> = map
1218 .nodes
1219 .iter()
1220 .enumerate()
1221 .filter(|(at, n)| {
1222 let offset = if *at == 0 { mine } else { n.offset };
1223 n.master == Some(node) && n.flags & FLAG_FAIL == 0 && offset != 0
1224 })
1225 .map(|(_, n)| n)
1226 .collect();
1227 out.array(3 + replicas.len());
1228 out.int(i64::from(from));
1229 out.int(i64::from(to));
1230 let held = &map.nodes[node as usize];
1231 for held in std::iter::once(held).chain(replicas.iter().copied()) {
1232 out.array(4);
1233 out.bulk(held.host.as_bytes());
1234 out.int(i64::from(held.port));
1235 out.bulk(held.id.as_bytes());
1236 out.array(0);
1237 }
1238}
1239
1240fn shards(server: &Server, out: &mut Out) {
1243 let map = server.cluster.map.lock();
1244 let at = out.len();
1245 let mut n = 0;
1246 let mut done: Vec<&str> = Vec::new();
1247 for node in 0..map.nodes.len() {
1248 let shard = map.nodes[node].shard.as_str();
1249 if done.contains(&shard) {
1250 continue;
1251 }
1252 done.push(shard);
1253 let members: Vec<usize> = (0..map.nodes.len())
1254 .filter(|other| map.nodes[*other].shard == shard)
1255 .collect();
1256 let runs: Vec<(u16, u16)> = members
1260 .iter()
1261 .flat_map(|member| map.runs(*member as u16))
1262 .collect();
1263 out.map(2);
1264 out.bulk(b"slots");
1265 out.array(runs.len() * 2);
1266 for (from, to) in runs {
1267 out.int(i64::from(from));
1268 out.int(i64::from(to));
1269 }
1270 out.bulk(b"nodes");
1271 out.array(members.len());
1272 for member in members {
1273 let held = &map.nodes[member];
1274 out.map(7);
1275 out.bulk(b"id");
1276 out.bulk(held.id.as_bytes());
1277 out.bulk(b"port");
1278 out.int(i64::from(held.port));
1279 out.bulk(b"ip");
1280 out.bulk(held.host.as_bytes());
1281 out.bulk(b"endpoint");
1282 out.bulk(held.host.as_bytes());
1283 out.bulk(b"role");
1284 out.bulk(if held.is_master() {
1285 b"master".as_slice()
1286 } else {
1287 b"replica".as_slice()
1288 });
1289 out.bulk(b"replication-offset");
1290 out.int(if member == 0 {
1291 server.repl_offset() as i64
1292 } else {
1293 held.offset as i64
1294 });
1295 out.bulk(b"health");
1296 out.bulk(match held.flags {
1297 f if f & FLAG_FAIL != 0 => b"fail".as_slice(),
1298 f if f & FLAG_PFAIL != 0 => b"loading".as_slice(),
1299 _ => b"online".as_slice(),
1300 });
1301 }
1302 n += 1;
1303 }
1304 out.close_array(at, n);
1305}
1306
1307fn help(out: &mut Out) {
1309 const LINES: &[&str] = &[
1310 "CLUSTER <subcommand> [<arg> [value] [opt] ...]. Subcommands are:",
1311 "COUNTKEYSINSLOT <slot>",
1312 " Return the number of keys in <slot>.",
1313 "GETKEYSINSLOT <slot> <count>",
1314 " Return key names stored by current node in a slot.",
1315 "INFO",
1316 " Return information about the cluster.",
1317 "KEYSLOT <key>",
1318 " Return the hash slot for <key>.",
1319 "MYID",
1320 " Return the node id.",
1321 "MYSHARDID",
1322 " Return the node's shard id.",
1323 "NODES",
1324 " Return cluster configuration seen by node. Output format:",
1325 " <id> <ip:port@bus-port[,hostname]> <flags> <master> <pings> <pongs> <epoch> <link> <slot> ...",
1326 "REPLICAS <node-id>",
1327 " Return <node-id> replicas.",
1328 "SLOTS",
1329 " Return information about slots range mappings. Each range is made of:",
1330 " start, end, master and replicas IP addresses, ports and ids",
1331 "SLOT-STATS",
1332 " Return an array of slot usage statistics for slots assigned to the current node.",
1333 "SHARDS",
1334 " Return information about slot range mappings and the nodes associated with them.",
1335 "ADDSLOTS <slot> [<slot> ...]",
1336 " Assign slots to current node.",
1337 "ADDSLOTSRANGE <start slot> <end slot> [<start slot> <end slot> ...]",
1338 " Assign slots which are between <start-slot> and <end-slot> to current node.",
1339 "BUMPEPOCH",
1340 " Advance the cluster config epoch.",
1341 "COUNT-FAILURE-REPORTS <node-id>",
1342 " Return number of failure reports for <node-id>.",
1343 "DELSLOTS <slot> [<slot> ...]",
1344 " Delete slots information from current node.",
1345 "DELSLOTSRANGE <start slot> <end slot> [<start slot> <end slot> ...]",
1346 " Delete slots information which are between <start-slot> and <end-slot> from current node.",
1347 "FAILOVER [FORCE|TAKEOVER]",
1348 " Promote current replica node to being a master.",
1349 "FORGET <node-id>",
1350 " Remove a node from the cluster.",
1351 "FLUSHSLOTS",
1352 " Delete current node own slots information.",
1353 "MEET <ip> <port> [<bus-port>]",
1354 " Connect nodes into a working cluster.",
1355 "REPLICATE <node-id>",
1356 " Configure current node as replica to <node-id>.",
1357 "RESET [HARD|SOFT]",
1358 " Reset current node (default: soft).",
1359 "SET-CONFIG-EPOCH <epoch>",
1360 " Set config epoch of current node.",
1361 "SETSLOT <slot> (IMPORTING <node-id>|MIGRATING <node-id>|STABLE|NODE <node-id>)",
1362 " Set slot state.",
1363 "SAVECONFIG",
1364 " Force saving cluster configuration on disk.",
1365 "LINKS",
1366 " Return information about all network links between this node and its peers.",
1367 " Output format is an array where each array element is a map containing attributes of a link",
1368 "MIGRATION IMPORT <start-slot end-slot [start-slot end-slot ...]> |",
1369 " STATUS [ID <task-id> | ALL] | CANCEL [ID <task-id> | ALL]",
1370 " Start, monitor and cancel slot migration.",
1371 "HELP",
1372 " Print this help.",
1373 ];
1374 out.array(LINES.len());
1375 for line in LINES {
1376 out.simple(line.as_bytes());
1377 }
1378}
1379
1380fn slot_arg(args: &Args<'_>, at: usize) -> Result<u16> {
1389 args.int(at)
1390 .ok()
1391 .and_then(|n| u16::try_from(n).ok())
1392 .filter(|s| usize::from(*s) < SLOTS)
1393 .ok_or_else(|| Error::new(Code::Invalid, "Invalid or out of range slot"))
1394}
1395
1396fn add_or_del(server: &Server, args: Args<'_>, add: bool, ranged: bool) -> Result<()> {
1403 let stride = if ranged { 2 } else { 1 };
1404 if ranged && !(args.len() - 2).is_multiple_of(2) {
1405 return Err(wrong_sub_arity(args.get(1)));
1406 }
1407 let mut wanted = Vec::new();
1408 let mut at = 2;
1409 while at < args.len() {
1410 let from = slot_arg(&args, at)?;
1411 let to = if ranged {
1412 slot_arg(&args, at + 1)?
1413 } else {
1414 from
1415 };
1416 if from > to {
1417 return Err(Error::fmt(
1418 Code::Invalid,
1419 format_args!("start slot number {from} is greater than end slot number {to}"),
1420 ));
1421 }
1422 for slot in from..=to {
1423 wanted.push(slot);
1424 }
1425 at += stride;
1426 }
1427 {
1428 let mut map = server.cluster.map.lock();
1429 let mut seen = vec![false; SLOTS];
1430 for slot in &wanted {
1431 let slot = usize::from(*slot);
1432 if seen[slot] {
1433 return Err(Error::fmt(
1434 Code::Invalid,
1435 format_args!("Slot {slot} specified multiple times"),
1436 ));
1437 }
1438 seen[slot] = true;
1439 let busy = map.owner[slot].is_some();
1440 if add && busy {
1441 return Err(Error::fmt(
1442 Code::Invalid,
1443 format_args!("Slot {slot} is already busy"),
1444 ));
1445 }
1446 if !add && !busy {
1447 return Err(Error::fmt(
1448 Code::Invalid,
1449 format_args!("Slot {slot} is already unassigned"),
1450 ));
1451 }
1452 }
1453 for slot in &wanted {
1454 let slot = usize::from(*slot);
1455 map.owner[slot] = if add { Some(0) } else { None };
1456 map.migrating[slot] = None;
1457 map.importing[slot] = None;
1458 }
1459 }
1460 server.recount_coverage();
1461 save(server)
1462}
1463
1464fn meet(args: &Args<'_>) -> Result<(String, u16, u16)> {
1470 let host = String::from_utf8_lossy(args.get(2));
1471 let typed = String::from_utf8_lossy(args.get(3));
1472 let port = args.int(3).map_err(|_| {
1473 Error::fmt(
1474 Code::Invalid,
1475 format_args!("Invalid base port specified: {typed}"),
1476 )
1477 })?;
1478 let bus = match args.opt(4) {
1479 None => port + i64::from(BUS_OFFSET),
1480 Some(word) => args.int(4).map_err(|_| {
1481 Error::fmt(
1482 Code::Invalid,
1483 format_args!(
1484 "Invalid bus port specified: {}",
1485 String::from_utf8_lossy(word)
1486 ),
1487 )
1488 })?,
1489 };
1490 if !(1..=65535).contains(&port) || !(0..=65535).contains(&bus) {
1491 return Err(Error::fmt(
1492 Code::Invalid,
1493 format_args!("Invalid node address specified: {host}:{typed}"),
1494 ));
1495 }
1496 let bus = if bus == 0 {
1497 port + i64::from(BUS_OFFSET)
1498 } else {
1499 bus
1500 };
1501 let host = yo_alloc::allow(|| host.into_owned());
1502 Ok((host, port as u16, bus as u16))
1503}
1504
1505fn replicas(server: &Server, id: &[u8], out: &mut Out) -> Result<()> {
1507 let map = server.cluster.map.lock();
1508 let Some(at) = map.find(id) else {
1509 return Err(unknown_node(id));
1510 };
1511 if !map.nodes[usize::from(at)].is_master() {
1512 return Err(Error::new(
1513 Code::Invalid,
1514 "The specified node is not a master",
1515 ));
1516 }
1517 let start = out.len();
1518 let mut n = 0;
1519 for other in 0..map.nodes.len() as u16 {
1520 if map.nodes[usize::from(other)].master != Some(at) {
1521 continue;
1522 }
1523 let line = yo_alloc::allow(|| {
1524 let mut s = String::with_capacity(256);
1525 describe(&map, other, false, &mut s);
1526 s
1527 });
1528 out.bulk(line.as_bytes());
1529 n += 1;
1530 }
1531 out.close_array(start, n);
1532 Ok(())
1533}
1534
1535fn forget(server: &Server, id: &[u8]) -> Result<()> {
1542 let at = {
1543 let map = server.cluster.map.lock();
1544 match map.find(id) {
1545 Some(0) => {
1546 return Err(Error::new(
1547 Code::Invalid,
1548 "I tried hard but I can't forget myself...",
1549 ));
1550 }
1551 Some(at) if map.nodes[0].master == Some(at) => {
1552 return Err(Error::new(Code::Invalid, "Can't forget my master!"));
1553 }
1554 Some(at) => at,
1555 None => {
1556 let name = String::from_utf8_lossy(id);
1557 if server.cluster_blacklisted(&name) {
1558 return Ok(());
1559 }
1560 return Err(unknown_node(id));
1561 }
1562 }
1563 };
1564 server.cluster_forget(at);
1565 Ok(())
1566}
1567
1568fn replicate(server: &Server, db: usize, id: &[u8]) -> Result<()> {
1574 let at = {
1575 let map = server.cluster.map.lock();
1576 match map.find(id) {
1577 None => return Err(unknown_node(id)),
1578 Some(0) => return Err(Error::new(Code::Invalid, "Can't replicate myself")),
1579 Some(at) if !map.nodes[usize::from(at)].is_master() => {
1580 return Err(Error::new(
1581 Code::Invalid,
1582 "I can only replicate a master, not a replica.",
1583 ));
1584 }
1585 Some(at) => {
1586 if map.nodes[0].is_master()
1587 && (!map.runs(0).is_empty() || !server.dbs[db].is_empty())
1588 {
1589 return Err(Error::new(
1590 Code::Invalid,
1591 "To set a master the node must be empty and without assigned slots.",
1592 ));
1593 }
1594 at
1595 }
1596 }
1597 };
1598 let Some(shared) = server.myself() else {
1599 return Err(Error::new(
1600 Code::Invalid,
1601 "CLUSTER REPLICATE is not available on an embedded server",
1602 ));
1603 };
1604 shared.cluster_replicate(at);
1605 Ok(())
1606}
1607
1608fn slot_stats(server: &Server, db: usize, args: Args<'_>, out: &mut Out) -> Result<()> {
1616 let sub = args.get(1);
1617 let mut wanted: Vec<u16> = Vec::new();
1618 let mut limit = SLOTS;
1619 let mut ascending = false;
1620 let ordered = args::is(args.get(2), b"orderby");
1621 if args::is(args.get(2), b"slotsrange") {
1622 if args.len() != 5 {
1623 return Err(sub_syntax(sub));
1624 }
1625 let from = slot_arg(&args, 3)?;
1626 let to = slot_arg(&args, 4)?;
1627 if from > to {
1628 return Err(Error::fmt(
1629 Code::Invalid,
1630 format_args!("Start slot number {from} is greater than end slot number {to}"),
1631 ));
1632 }
1633 wanted.extend(from..=to);
1634 } else if ordered {
1635 if !args::is(args.get(3), b"key-count") {
1636 return Err(Error::new(
1637 Code::Invalid,
1638 "Unrecognized sort metric for ORDERBY.",
1639 ));
1640 }
1641 let bad_limit = || {
1642 Error::new(
1643 Code::Invalid,
1644 "Limit has to lie in between 1 and 16384 (maximum number of slots).",
1645 )
1646 };
1647 let mut at = 4;
1648 while at < args.len() {
1649 let word = args.get(at);
1650 if args::is(word, b"limit") && at + 1 < args.len() {
1651 let n = args.int(at + 1).map_err(|_| bad_limit())?;
1652 if !(1..=SLOTS as i64).contains(&n) {
1653 return Err(bad_limit());
1654 }
1655 limit = n as usize;
1656 at += 2;
1657 } else if args::is(word, b"asc") {
1658 ascending = true;
1659 at += 1;
1660 } else if args::is(word, b"desc") {
1661 at += 1;
1662 } else {
1663 return Err(args::syntax());
1664 }
1665 }
1666 wanted.extend(0..SLOTS as u16);
1667 } else {
1668 return Err(sub_syntax(sub));
1669 }
1670 let mut counts = vec![0i64; SLOTS];
1671 server.dbs[db].keys(|key| counts[key_slot(key) as usize] += 1);
1672 let map = server.cluster.map.lock();
1673 wanted.retain(|slot| map.mine(*slot));
1674 drop(map);
1675 if ordered {
1676 if ascending {
1679 wanted.sort_by_key(|slot| (counts[*slot as usize], *slot));
1680 } else {
1681 wanted.sort_by_key(|slot| (-counts[*slot as usize], *slot));
1682 }
1683 wanted.truncate(limit);
1684 }
1685 out.array(wanted.len());
1686 for slot in wanted {
1687 out.array(2);
1688 out.int(i64::from(slot));
1689 out.map(1);
1690 out.bulk(b"key-count");
1691 out.int(counts[slot as usize]);
1692 }
1693 Ok(())
1694}
1695
1696fn migration(server: &Server, args: Args<'_>, out: &mut Out) -> Result<()> {
1704 let sub = args.get(1);
1705 let action = args.get(2);
1706 if args::is(action, b"status") || args::is(action, b"cancel") {
1707 let by_id = args::is(args.get(3), b"id");
1708 if by_id && args.len() != 5 {
1709 return Err(wrong_sub_arity(sub));
1710 }
1711 if !by_id && !args::is(args.get(3), b"all") {
1712 return Err(Error::new(Code::Invalid, "unknown argument"));
1713 }
1714 if !by_id && args.len() != 4 {
1715 return Err(wrong_sub_arity(sub));
1716 }
1717 if args::is(action, b"status") {
1718 out.array(0);
1719 } else {
1720 out.int(0);
1721 }
1722 return Ok(());
1723 }
1724 if !args::is(action, b"import") {
1725 return Err(Error::new(Code::Invalid, "unknown argument"));
1726 }
1727 let ranges = slot_ranges(&args, 3)?;
1728 let map = server.cluster.map.lock();
1729 if ranges
1730 .iter()
1731 .flat_map(|(from, to)| *from..=*to)
1732 .all(|slot| map.mine(slot))
1733 {
1734 return Err(Error::new(
1735 Code::Invalid,
1736 "this node is already the owner of the slot range",
1737 ));
1738 }
1739 drop(map);
1740 Err(not_yet("CLUSTER MIGRATION IMPORT"))
1741}
1742
1743fn slot_ranges(args: &Args<'_>, from: usize) -> Result<Vec<(u16, u16)>> {
1752 let count = args.len().saturating_sub(from);
1753 if count < 2 || !count.is_multiple_of(2) {
1754 return Err(wrong_sub_arity(args.get(1)));
1755 }
1756 if count / 2 >= SLOTS {
1757 return Err(Error::fmt(
1758 Code::Invalid,
1759 format_args!("invalid number of slot ranges: {}", count / 2),
1760 ));
1761 }
1762 let mut ranges: Vec<(u16, u16)> = Vec::with_capacity(count / 2);
1763 let mut at = from;
1764 while at < args.len() {
1765 ranges.push((slot_arg(args, at)?, slot_arg(args, at + 1)?));
1766 at += 2;
1767 }
1768 ranges.sort_unstable();
1769 let mut joined: Vec<(u16, u16)> = Vec::with_capacity(ranges.len());
1770 for range in ranges {
1771 match joined.last_mut() {
1772 Some(last) if u32::from(last.1) + 1 == u32::from(range.0) => last.1 = range.1,
1773 _ => joined.push(range),
1774 }
1775 }
1776 let mut seen = vec![false; SLOTS];
1777 for &(start, end) in &joined {
1778 if start > end {
1779 return Err(Error::fmt(
1780 Code::Invalid,
1781 format_args!("start slot number {start} is greater than end slot number {end}"),
1782 ));
1783 }
1784 for slot in start..=end {
1785 if core::mem::replace(&mut seen[usize::from(slot)], true) {
1786 return Err(Error::fmt(
1787 Code::Invalid,
1788 format_args!("Slot {slot} specified multiple times"),
1789 ));
1790 }
1791 }
1792 }
1793 Ok(joined)
1794}
1795
1796fn syncslots(server: &Server, session: &mut Session, args: Args<'_>, out: &mut Out) -> Result<()> {
1812 if !session.internal() {
1813 session.hang_up();
1817 return Err(Error::new(
1818 Code::Invalid,
1819 "CLUSTER SYNCSLOTS subcommands are only allowed for internal clients",
1820 ));
1821 }
1822 let action = args.get(2);
1823 if !server.cluster.map.lock().nodes[0].is_master() {
1824 if !session.serving_master() {
1831 session.hang_up();
1832 return Err(Error::new(
1833 Code::Invalid,
1834 "CLUSTER SYNCSLOTS subcommands are only allowed for master",
1835 ));
1836 }
1837 if !args::is(action, b"conf") {
1838 return Ok(());
1839 }
1840 }
1841 if args::is(action, b"sync") && args.len() >= 6 {
1842 return sync(server, args);
1843 }
1844 if args::is(action, b"rdbchannel") && args.len() == 4 {
1845 if args.get(3).len() != ID_LEN {
1846 return Err(Error::new(Code::Invalid, "Invalid task id"));
1847 }
1848 return Err(Error::new(
1849 Code::Invalid,
1850 "No slot migration task in progress",
1851 ));
1852 }
1853 if (args::is(action, b"snapshot-eof") || args::is(action, b"stream-eof")) && args.len() == 3 {
1854 session.hang_up();
1858 return Ok(());
1859 }
1860 if (args::is(action, b"ack") && args.len() == 5)
1861 || (args::is(action, b"fail") && args.len() == 4)
1862 {
1863 return Ok(());
1868 }
1869 if args::is(action, b"conf") && args.len() >= 5 {
1870 return conf(server, args, out);
1871 }
1872 Err(args::syntax())
1873}
1874
1875fn sync(server: &Server, args: Args<'_>) -> Result<()> {
1882 if !args.len().is_multiple_of(2) {
1883 return Err(wrong_sub_arity(args.get(1)));
1884 }
1885 let ranges = slot_ranges(&args, 4)?;
1886 {
1887 let map = server.cluster.map.lock();
1888 if (0..SLOTS).any(|at| map.migrating[at].is_some() || map.importing[at].is_some()) {
1892 return Err(Error::new(
1893 Code::Invalid,
1894 "all slot states must be STABLE to start a slot migration task.",
1895 ));
1896 }
1897 let mut source = None;
1898 for slot in ranges.iter().flat_map(|(from, to)| *from..=*to) {
1899 let Some(owner) = map.owner[usize::from(slot)] else {
1900 return Err(Error::fmt(
1901 Code::Invalid,
1902 format_args!("slot has no owner: {slot}"),
1903 ));
1904 };
1905 if *source.get_or_insert(owner) != owner {
1906 return Err(Error::new(
1907 Code::Invalid,
1908 "slots belong to different source nodes",
1909 ));
1910 }
1911 }
1912 if source != Some(0) {
1913 return Err(Error::new(
1914 Code::Invalid,
1915 "This node is not the owner of the slots",
1916 ));
1917 }
1918 }
1919 Err(not_yet("CLUSTER SYNCSLOTS SYNC"))
1920}
1921
1922fn conf(server: &Server, args: Args<'_>, out: &mut Out) -> Result<()> {
1936 let mut at = 3;
1937 while at < args.len() {
1938 if at + 1 >= args.len() {
1939 super::write_error(out, &wrong_sub_arity(args.get(1)));
1940 return Ok(());
1941 }
1942 let name = args.get(at);
1943 let value = args.get(at + 1);
1944 if args::is(name, b"node-id") {
1945 if value.len() != ID_LEN {
1948 let len = value.len();
1949 super::write_error(
1950 out,
1951 &Error::fmt(Code::Invalid, format_args!("Invalid node id length {len}")),
1952 );
1953 return Ok(());
1954 }
1955 if server.cluster.map.lock().find(value).is_none() {
1956 super::write_error(
1957 out,
1958 &Error::fmt(
1959 Code::Invalid,
1960 format_args!(
1961 "Node {} not found in cluster",
1962 String::from_utf8_lossy(value)
1963 ),
1964 ),
1965 );
1966 return Ok(());
1967 }
1968 } else if args::is(name, b"slot-info") {
1969 if !slot_info(value) {
1970 super::write_error(
1971 out,
1972 &Error::fmt(
1973 Code::Invalid,
1974 format_args!("Invalid slot info: {}", String::from_utf8_lossy(value)),
1975 ),
1976 );
1977 return Ok(());
1978 }
1979 } else if args::is(name, b"asm-task") {
1980 if server.cluster.map.lock().nodes[0].is_master() {
1981 super::write_error(
1982 out,
1983 &Error::new(
1984 Code::Invalid,
1985 "CLUSTER SYNCSLOTS CONF ASM-TASK only allowed on replica",
1986 ),
1987 );
1988 return Ok(());
1989 }
1990 super::write_error(
1995 out,
1996 &Error::fmt(
1997 Code::Invalid,
1998 format_args!(
1999 "Failed to handle master task: {}",
2000 String::from_utf8_lossy(value)
2001 ),
2002 ),
2003 );
2004 } else if !args::is(name, b"capa") {
2005 super::write_error(
2006 out,
2007 &Error::fmt(
2008 Code::Invalid,
2009 format_args!("Unknown option {}", String::from_utf8_lossy(name)),
2010 ),
2011 );
2012 }
2013 at += 2;
2014 }
2015 out.ok();
2016 Ok(())
2017}
2018
2019fn slot_info(value: &[u8]) -> bool {
2027 let mut parts = value.split(|b| *b == b':');
2028 let Some(slot) = parts.next().and_then(yo_common::num::parse_i64) else {
2029 return false;
2030 };
2031 let Some(keys) = parts.next().and_then(yo_common::num::parse_i64) else {
2032 return false;
2033 };
2034 let Some(expires) = parts.next().and_then(yo_common::num::parse_i64) else {
2035 return false;
2036 };
2037 parts.next().is_none() && (0..SLOTS as i64).contains(&slot) && keys >= 0 && expires >= 0
2038}
2039
2040fn setslot(server: &Server, session_db: usize, args: Args<'_>, out: &mut Out) -> Result<()> {
2053 if !server.cluster.map.lock().nodes[0].is_master() {
2056 return Err(Error::new(
2057 Code::Invalid,
2058 "Please use SETSLOT only with masters.",
2059 ));
2060 }
2061 let slot = slot_arg(&args, 2)?;
2062 let action = args.get(3);
2063 let wrong = || {
2064 Error::new(
2065 Code::Invalid,
2066 "Invalid CLUSTER SETSLOT action or number of arguments. Try CLUSTER HELP",
2067 )
2068 };
2069 let mut announce = false;
2073 let mut follow: Option<u16> = None;
2076 {
2077 let mut map = server.cluster.map.lock();
2078 let at = usize::from(slot);
2079 if args::is(action, b"migrating") && args.len() == 5 {
2080 if !map.mine(slot) {
2081 return Err(Error::fmt(
2082 Code::Invalid,
2083 format_args!("I'm not the owner of hash slot {slot}"),
2084 ));
2085 }
2086 let Some(to) = map.find(args.get(4)) else {
2087 return Err(dont_know(args.get(4)));
2088 };
2089 map.migrating[at] = Some(to);
2090 } else if args::is(action, b"importing") && args.len() == 5 {
2091 if map.mine(slot) {
2092 return Err(Error::fmt(
2093 Code::Invalid,
2094 format_args!("I'm already the owner of hash slot {slot}"),
2095 ));
2096 }
2097 let Some(from) = map.find(args.get(4)) else {
2098 return Err(dont_know(args.get(4)));
2099 };
2100 map.importing[at] = Some(from);
2101 } else if args::is(action, b"stable") && args.len() == 4 {
2102 map.migrating[at] = None;
2103 map.importing[at] = None;
2104 } else if args::is(action, b"node") && args.len() == 5 {
2105 let Some(to) = map.find(args.get(4)) else {
2106 return Err(unknown_node(args.get(4)));
2107 };
2108 if !map.nodes[usize::from(to)].is_master() {
2109 return Err(Error::new(Code::Invalid, "Target node is not a master"));
2110 }
2111 let was_mine = map.owner[at] == Some(0);
2112 let held = keys_in_slot(server, session_db, slot);
2120 if was_mine && to != 0 && held != 0 {
2121 return Err(Error::fmt(
2122 Code::Invalid,
2123 format_args!(
2124 "Can't assign hashslot {slot} to a different node while I still hold keys for this hash slot."
2125 ),
2126 ));
2127 }
2128 if held == 0 {
2129 map.migrating[at] = None;
2130 }
2131 map.owner[at] = Some(to);
2132 if was_mine && to != 0 && map.runs(0).is_empty() {
2136 follow = Some(to);
2137 }
2138 if to == 0 && map.importing[at].is_some() {
2143 bump_without_consensus(server, &mut map);
2144 map.importing[at] = None;
2145 announce = true;
2146 }
2147 } else {
2148 return Err(wrong());
2149 }
2150 }
2151 server.recount_coverage();
2152 save(server)?;
2153 if let Some(to) = follow
2156 && let Some(shared) = server.myself()
2157 {
2158 shared.cluster_replicate(to);
2159 }
2160 if announce {
2161 server.cluster_broadcast_pong();
2162 }
2163 out.ok();
2164 Ok(())
2165}
2166
2167fn keys_in_slot(server: &Server, at: usize, slot: u16) -> usize {
2170 let mut found = 0;
2171 server.dbs[at].keys(|key| {
2172 if key_slot(key) == slot {
2173 found += 1;
2174 }
2175 });
2176 found
2177}
2178
2179fn bump_without_consensus(server: &Server, map: &mut Map) -> bool {
2193 let highest = map
2194 .nodes
2195 .iter()
2196 .map(|n| n.epoch)
2197 .max()
2198 .unwrap_or(0)
2199 .max(server.cluster.epoch.load(Relaxed));
2200 let mine = map.nodes[0].epoch;
2201 if mine != 0 && mine == highest {
2202 return false;
2203 }
2204 map.nodes[0].epoch = server.cluster.epoch.fetch_add(1, Relaxed) + 1;
2205 true
2206}
2207
2208fn flushslots(server: &Server, out: &mut Out) -> Result<()> {
2210 if server.dbs.iter().any(|db| !db.is_empty()) {
2211 return Err(Error::new(
2212 Code::Invalid,
2213 "DB must be empty to perform CLUSTER FLUSHSLOTS.",
2214 ));
2215 }
2216 {
2217 let mut map = server.cluster.map.lock();
2218 for slot in 0..SLOTS {
2219 if map.owner[slot] == Some(0) {
2220 map.owner[slot] = None;
2221 }
2222 map.migrating[slot] = None;
2223 map.importing[slot] = None;
2224 }
2225 }
2226 server.recount_coverage();
2227 save(server)?;
2228 out.ok();
2229 Ok(())
2230}
2231
2232fn bumpepoch(server: &Server, out: &mut Out) -> Result<()> {
2239 let (moved, epoch) = {
2240 let mut map = server.cluster.map.lock();
2241 let moved = bump_without_consensus(server, &mut map);
2242 (moved, map.nodes[0].epoch)
2243 };
2244 if moved {
2245 save(server)?;
2246 server.cluster_broadcast_pong();
2247 }
2248 let word = if moved { "BUMPED" } else { "STILL" };
2249 let text = yo_alloc::allow(|| format!("{word} {epoch}"));
2250 out.simple(text.as_bytes());
2251 Ok(())
2252}
2253
2254fn set_config_epoch(server: &Server, args: Args<'_>, out: &mut Out) -> Result<()> {
2257 let epoch = args.int(2)?;
2258 if epoch < 0 {
2259 return Err(Error::fmt(
2260 Code::Invalid,
2261 format_args!("Invalid config epoch specified: {epoch}"),
2262 ));
2263 }
2264 {
2265 let mut map = server.cluster.map.lock();
2266 if map.nodes[0].epoch != 0 {
2267 return Err(Error::new(
2268 Code::Invalid,
2269 "Node config epoch is already non-zero",
2270 ));
2271 }
2272 map.nodes[0].epoch = epoch as u64;
2273 }
2274 let epoch = epoch as u64;
2275 server.cluster.epoch.fetch_max(epoch, Relaxed);
2276 save(server)?;
2277 out.ok();
2278 Ok(())
2279}
2280
2281fn reset(server: &Server, args: Args<'_>, out: &mut Out) -> Result<()> {
2284 let hard = match args.opt(2) {
2285 None => false,
2286 Some(word) if args::is(word, b"hard") => true,
2287 Some(word) if args::is(word, b"soft") => false,
2288 Some(_) => return Err(args::syntax()),
2289 };
2290 if server.dbs.iter().any(|db| !db.is_empty()) {
2291 return Err(Error::new(
2292 Code::Invalid,
2293 "CLUSTER RESET can't be called with master nodes containing keys",
2294 ));
2295 }
2296 {
2297 let mut map = server.cluster.map.lock();
2298 for slot in 0..SLOTS {
2299 map.owner[slot] = None;
2300 map.migrating[slot] = None;
2301 map.importing[slot] = None;
2302 }
2303 map.nodes.truncate(1);
2304 map.nodes[0].epoch = 0;
2305 map.nodes[0].flags = FLAG_MYSELF | FLAG_MASTER;
2309 map.nodes[0].master = None;
2310 if hard {
2311 yo_alloc::allow(|| {
2312 map.nodes[0].id = String::from_utf8_lossy(&new_id()).into_owned();
2313 map.nodes[0].shard = String::from_utf8_lossy(&new_id()).into_owned();
2314 });
2315 }
2316 }
2317 if hard {
2318 server.cluster.epoch.store(0, Relaxed);
2319 }
2320 server.recount_coverage();
2321 save(server)?;
2322 out.ok();
2323 Ok(())
2324}
2325
2326fn count_keys(server: &Server, at: usize, args: Args<'_>, out: &mut Out) -> Result<()> {
2330 let n = args.int(2)?;
2331 let slot = u16::try_from(n)
2332 .ok()
2333 .filter(|s| usize::from(*s) < SLOTS)
2334 .ok_or_else(|| Error::new(Code::Invalid, "Invalid slot"))?;
2335 let mut found = 0i64;
2336 server.dbs[at].keys(|key| {
2337 if key_slot(key) == slot {
2338 found += 1;
2339 }
2340 });
2341 out.int(found);
2342 Ok(())
2343}
2344
2345fn get_keys(server: &Server, at: usize, args: Args<'_>, out: &mut Out) -> Result<()> {
2347 let slot = args.int(2)?;
2348 let count = args.int(3)?;
2349 let bad = || Error::new(Code::Invalid, "Invalid slot or number of keys");
2350 if !(0..SLOTS as i64).contains(&slot) || count < 0 {
2351 return Err(bad());
2352 }
2353 let slot = slot as u16;
2354 let want = count as usize;
2355 let start = out.len();
2356 let mut n = 0;
2357 server.dbs[at].keys(|key| {
2358 if n < want && key_slot(key) == slot {
2359 out.bulk(key);
2360 n += 1;
2361 }
2362 });
2363 out.close_array(start, n);
2364 Ok(())
2365}
2366
2367fn save(server: &Server) -> Result<()> {
2376 let path = {
2377 let file = server.cluster.file.lock();
2378 if file.is_empty() {
2379 return Ok(());
2380 }
2381 yo_alloc::allow(|| file.clone())
2382 };
2383 let epoch = server.cluster.epoch.load(Relaxed);
2384 let text = yo_alloc::allow(|| {
2385 let mut s = lines(server, true);
2386 let _ = writeln!(s, "vars currentEpoch {epoch} lastVoteEpoch 0");
2387 s
2388 });
2389 yo_alloc::allow(|| {
2390 let temp = format!("{path}.tmp");
2391 let wrote =
2392 std::fs::write(&temp, text.as_bytes()).and_then(|()| std::fs::rename(&temp, &path));
2393 match wrote {
2394 Ok(()) => Ok(()),
2395 Err(e) => Err(Error::fmt(
2396 Code::Invalid,
2397 format_args!("cluster config file could not be written: {e}"),
2398 )),
2399 }
2400 })
2401}
2402
2403impl Server {
2404 fn reload_cluster(&mut self) -> Result<()> {
2412 let path = self.cluster_file();
2413 if path.is_empty() {
2414 return Ok(());
2415 }
2416 let text = match yo_alloc::allow(|| std::fs::read_to_string(&path)) {
2417 Ok(text) => text,
2418 Err(e) if e.kind() == std::io::ErrorKind::NotFound => return Ok(()),
2419 Err(e) => {
2420 return Err(Error::fmt(Code::Invalid, format_args!("{path}: {e}")));
2421 }
2422 };
2423 yo_alloc::allow(|| self.absorb_cluster(&text))
2424 }
2425
2426 fn absorb_cluster(&mut self, text: &str) -> Result<()> {
2428 let bad = |what: &str| Error::fmt(Code::Invalid, format_args!("{what} in cluster config"));
2429 let now = self.now_ms();
2430 let mut nodes: Vec<Node> = Vec::new();
2431 let mut owned: Vec<(String, u16, u16)> = Vec::new();
2435 let mut follows: Vec<(String, String)> = Vec::new();
2438 for line in text.lines() {
2439 let line = line.trim();
2440 if line.is_empty() {
2441 continue;
2442 }
2443 let mut words = line.split(' ');
2444 let first = words.next().unwrap_or_default();
2445 if first == "vars" {
2446 let rest: Vec<&str> = words.collect();
2449 for pair in rest.chunks(2) {
2450 if pair.len() == 2 && pair[0] == "currentEpoch" {
2451 let epoch = pair[1].parse::<u64>().map_err(|_| bad("bad epoch"))?;
2452 self.cluster.epoch.store(epoch, Relaxed);
2453 }
2454 }
2455 continue;
2456 }
2457 if first.len() != ID_LEN {
2458 return Err(bad("bad node id"));
2459 }
2460 let address = words.next().ok_or_else(|| bad("missing address"))?;
2461 let named = words.next().ok_or_else(|| bad("missing flags"))?;
2462 let master = words.next().ok_or_else(|| bad("missing master"))?;
2463 let ping = words.next().ok_or_else(|| bad("missing ping time"))?;
2464 let pong = words.next().ok_or_else(|| bad("missing pong time"))?;
2465 let epoch = words
2466 .next()
2467 .and_then(|w| w.parse::<u64>().ok())
2468 .ok_or_else(|| bad("bad config epoch"))?;
2469 let _link = words.next();
2471 let (host, port, bus, shard) =
2472 split_address(address).ok_or_else(|| bad("bad address"))?;
2473 for word in words {
2474 if word.starts_with('[') {
2478 continue;
2479 }
2480 let (from, to) = match word.split_once('-') {
2481 Some((a, b)) => (
2482 a.parse::<u16>().map_err(|_| bad("bad slot"))?,
2483 b.parse::<u16>().map_err(|_| bad("bad slot"))?,
2484 ),
2485 None => {
2486 let one = word.parse::<u16>().map_err(|_| bad("bad slot"))?;
2487 (one, one)
2488 }
2489 };
2490 if usize::from(from) >= SLOTS || usize::from(to) >= SLOTS || from > to {
2491 return Err(bad("slot out of range"));
2492 }
2493 owned.push((first.to_owned(), from, to));
2494 }
2495 let mut flags = 0u16;
2496 for name in named.split(',') {
2497 flags |= match name {
2498 "myself" => FLAG_MYSELF,
2499 "master" => FLAG_MASTER,
2500 "slave" => FLAG_SLAVE,
2501 "fail?" => FLAG_PFAIL,
2502 "fail" => FLAG_FAIL,
2503 "handshake" => FLAG_HANDSHAKE,
2504 "noaddr" => FLAG_NOADDR,
2505 "nofailover" => FLAG_NOFAILOVER,
2506 _ => 0,
2507 };
2508 }
2509 if master != "-" {
2510 if master.len() != ID_LEN {
2511 return Err(bad("bad master id"));
2512 }
2513 follows.push((first.to_owned(), master.to_owned()));
2514 }
2515 let node = Node {
2516 id: first.to_owned(),
2517 host,
2518 port,
2519 bus,
2520 shard,
2521 epoch,
2522 flags,
2523 master: None,
2524 ping_sent: stamp(ping, now),
2529 pong_recv: stamp(pong, now),
2530 data_recv: now,
2531 fail_time: 0,
2532 offset: 0,
2533 linked: false,
2537 reports: Vec::new(),
2538 };
2539 if named.split(',').any(|f| f == "myself") {
2540 nodes.insert(0, node);
2542 } else {
2543 nodes.push(node);
2544 }
2545 }
2546 if nodes.is_empty() {
2547 return Ok(());
2548 }
2549 let mut map = Map::new(nodes.remove(0));
2550 map.nodes.append(&mut nodes);
2551 for (id, from, to) in owned {
2552 let Some(at) = map.find(id.as_bytes()) else {
2553 return Err(bad("slots for a node nobody knows"));
2554 };
2555 for slot in from..=to {
2556 map.owner[usize::from(slot)] = Some(at);
2557 }
2558 }
2559 for (who, whose) in follows {
2560 let (Some(who), Some(whose)) = (map.find(who.as_bytes()), map.find(whose.as_bytes()))
2561 else {
2562 return Err(bad("master id nobody knows"));
2563 };
2564 map.nodes[usize::from(who)].master = Some(whose);
2565 map.nodes[usize::from(who)].epoch = 0;
2570 }
2571 *self.cluster.map.lock() = map;
2572 Ok(())
2573 }
2574}
2575
2576fn stamp(field: &str, now: u64) -> u64 {
2583 match field.parse::<u64>() {
2584 Ok(0) | Err(_) => 0,
2585 Ok(_) => now,
2586 }
2587}
2588
2589fn split_address(field: &str) -> Option<(String, u16, u16, String)> {
2596 let (address, aux) = match field.split_once(',') {
2597 Some((address, aux)) => (address, aux),
2598 None => (field, ""),
2599 };
2600 let (host, ports) = address.rsplit_once(':')?;
2601 let (client, bus) = match ports.split_once('@') {
2602 Some((client, bus)) => (client, bus.parse::<u16>().ok()?),
2603 None => (ports, 0),
2604 };
2605 let port = client.parse::<u16>().ok()?;
2606 let bus = if bus == 0 { port + BUS_OFFSET } else { bus };
2607 let shard = aux
2608 .split(',')
2609 .find_map(|pair| pair.strip_prefix("shard-id="))
2610 .map_or_else(
2611 || String::from_utf8_lossy(&new_id()).into_owned(),
2612 str::to_owned,
2613 );
2614 Some((host.to_owned(), port, bus, shard))
2615}
2616
2617#[cfg(test)]
2627impl Server {
2628 pub(super) fn cluster_own_everything(&self) {
2630 {
2631 let mut map = self.cluster.map.lock();
2632 for slot in 0..SLOTS {
2633 map.owner[slot] = Some(0);
2634 }
2635 }
2636 self.recount_coverage();
2637 self.cluster.was_down.store(false, Relaxed);
2638 self.cluster.booted_at.store(0, Relaxed);
2639 }
2640
2641 pub(super) fn cluster_pretend_node(&self, id: &str, host: &str, port: u16) -> u16 {
2643 let mut map = self.cluster.map.lock();
2644 let mut node = Node::new(
2645 id.to_owned(),
2646 host.to_owned(),
2647 port,
2648 port + BUS_OFFSET,
2649 FLAG_MASTER,
2650 0,
2651 );
2652 node.shard = id.to_owned();
2653 node.linked = true;
2654 map.nodes.push(node);
2655 (map.nodes.len() - 1) as u16
2656 }
2657
2658 pub(super) fn cluster_hand_over(&self, slot: u16, node: u16) {
2660 let mut map = self.cluster.map.lock();
2661 map.owner[usize::from(slot)] = Some(node);
2662 }
2663
2664 pub(super) fn cluster_pretend_follower(&self, of: u16) {
2670 let mut map = self.cluster.map.lock();
2671 map.nodes[0].flags &= !FLAG_MASTER;
2672 map.nodes[0].flags |= FLAG_SLAVE;
2673 map.nodes[0].master = Some(of);
2674 }
2675
2676 pub(super) fn cluster_moving(&self, slot: u16, to: Option<u16>, from: Option<u16>) {
2678 let mut map = self.cluster.map.lock();
2679 map.migrating[usize::from(slot)] = to;
2680 map.importing[usize::from(slot)] = from;
2681 }
2682}
2683
2684#[cfg(test)]
2687mod tests {
2688 use super::{SLOTS, key_slot};
2689
2690 #[test]
2693 fn a_key_lands_in_the_slot_a_real_server_puts_it_in() {
2694 assert_eq!(key_slot(b"foo"), 12182);
2695 assert_eq!(key_slot(b"1234"), 6025);
2696 assert_eq!(key_slot(b""), 0);
2697 assert_eq!(key_slot(b"{user1000}.following"), 3443);
2698 }
2699
2700 #[test]
2703 fn the_hash_tag_rules_are_the_reference_rules() {
2704 assert_eq!(
2706 key_slot(b"{user1000}.following"),
2707 key_slot(b"{user1000}.followers")
2708 );
2709 assert_eq!(key_slot(b"{}foo"), key_slot(b"{}foo"));
2711 assert_ne!(key_slot(b"{}foo"), key_slot(b"foo"));
2712 assert_ne!(key_slot(b"{foo"), key_slot(b"foo"));
2714 assert_eq!(key_slot(b"{a}{b}"), key_slot(b"a"));
2716 assert_eq!(key_slot(b"foo{{bar}}zap"), key_slot(b"{bar"));
2718 }
2719
2720 #[test]
2723 fn every_slot_is_in_range() {
2724 let mut seen = vec![false; SLOTS];
2725 for i in 0..200_000u32 {
2726 let key = i.to_string();
2727 let slot = key_slot(key.as_bytes());
2728 assert!(usize::from(slot) < SLOTS);
2729 seen[usize::from(slot)] = true;
2730 }
2731 assert!(seen.iter().all(|s| *s), "200k keys reach all 16384 slots");
2732 }
2733
2734 #[test]
2743 fn a_config_file_puts_every_run_on_the_node_that_owns_it() {
2744 let mut server = super::Server::new();
2745 server.enable_cluster("", 7355);
2746 let text = "\
27473b80b05445f38bc7214f083696a2bbf90e3f30e3 127.0.0.1:7356@17356 master - 0 0 0 connected 10923-16383
274830d0651b0ec5e178e082634c44fb9adcc6e4021b 127.0.0.1:7355@17355 myself,master - 0 0 1 connected 5461-10922
274919a9e69b8b66016ac43c55ccdeed0283e0148e17 127.0.0.1:7354@17354 master - 0 0 2 connected 0-5460
2750vars currentEpoch 2 lastVoteEpoch 0
2751";
2752 server.absorb_cluster(text).expect("the file parses");
2753 let map = server.cluster.map.lock();
2754 let at = |id: &str| map.find(id.as_bytes()).expect("the node is in the table");
2755 assert_eq!(at("30d0651b0ec5e178e082634c44fb9adcc6e4021b"), 0, "myself");
2756 for (id, from, to) in [
2757 ("19a9e69b8b66016ac43c55ccdeed0283e0148e17", 0, 5460),
2758 ("30d0651b0ec5e178e082634c44fb9adcc6e4021b", 5461, 10922),
2759 ("3b80b05445f38bc7214f083696a2bbf90e3f30e3", 10923, 16383),
2760 ] {
2761 let owner = Some(at(id));
2762 for slot in from..=to {
2763 assert_eq!(map.owner[slot], owner, "slot {slot} belongs to {id}");
2764 }
2765 }
2766 }
2767
2768 #[test]
2772 fn a_config_file_is_not_read_back_as_live_state() {
2773 let mut server = super::Server::new();
2774 server.enable_cluster("", 7357);
2775 let text = "\
27769fcbb7624dedbb2fd0020dd2fbf86a5eb8cec31b 127.0.0.1:7355@17355 master - 0 1789005531306 3 connected 5461-10922
2777ac6dd51a69741dc5130637c594866c9b7e0cfc4e 127.0.0.1:7357@17357 myself,slave 9fcbb7624dedbb2fd0020dd2fbf86a5eb8cec31b 1789005400000 1789005532315 3 connected
2778";
2779 server.absorb_cluster(text).expect("the file parses");
2780 let now = server.now_ms();
2781 let map = server.cluster.map.lock();
2782 for node in &map.nodes {
2783 assert!(!node.linked, "nothing is linked before the bus dials out");
2784 }
2785 assert_eq!(map.nodes[0].ping_sent, now, "myself had a ping in flight");
2788 assert_eq!(map.nodes[1].ping_sent, 0, "the master did not");
2789 assert_eq!(map.nodes[0].pong_recv, now);
2790 assert_eq!(map.nodes[0].epoch, 0, "the replica's epoch is dropped");
2792 assert_eq!(map.nodes[1].epoch, 3, "the master's is kept");
2793 }
2794}