1use core::fmt::Write as _;
51use std::sync::atomic::Ordering::Relaxed;
52use std::sync::atomic::{AtomicBool, AtomicU64};
53
54use yo_common::lock::Lock;
55use yo_common::{Code, Error, Result};
56
57use crate::reply::Out;
58
59use super::args::{self, Args};
60use super::{Server, Session};
61
62mod asm;
63mod bus;
64mod import;
65use super::keyspec;
66use super::table::Spec;
67pub(super) use asm::Migration;
68
69pub const SLOTS: usize = 16384;
75
76const WRITABLE_DELAY_MS: u64 = 2000;
83
84const REJOIN_DELAY_MS: u64 = 5000;
94
95const BUS_OFFSET: u16 = 10000;
97
98const ID_LEN: usize = 40;
100
101#[rustfmt::skip]
111const CRC16: [u16; 256] = [
112 0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50a5, 0x60c6, 0x70e7,
113 0x8108, 0x9129, 0xa14a, 0xb16b, 0xc18c, 0xd1ad, 0xe1ce, 0xf1ef,
114 0x1231, 0x0210, 0x3273, 0x2252, 0x52b5, 0x4294, 0x72f7, 0x62d6,
115 0x9339, 0x8318, 0xb37b, 0xa35a, 0xd3bd, 0xc39c, 0xf3ff, 0xe3de,
116 0x2462, 0x3443, 0x0420, 0x1401, 0x64e6, 0x74c7, 0x44a4, 0x5485,
117 0xa56a, 0xb54b, 0x8528, 0x9509, 0xe5ee, 0xf5cf, 0xc5ac, 0xd58d,
118 0x3653, 0x2672, 0x1611, 0x0630, 0x76d7, 0x66f6, 0x5695, 0x46b4,
119 0xb75b, 0xa77a, 0x9719, 0x8738, 0xf7df, 0xe7fe, 0xd79d, 0xc7bc,
120 0x48c4, 0x58e5, 0x6886, 0x78a7, 0x0840, 0x1861, 0x2802, 0x3823,
121 0xc9cc, 0xd9ed, 0xe98e, 0xf9af, 0x8948, 0x9969, 0xa90a, 0xb92b,
122 0x5af5, 0x4ad4, 0x7ab7, 0x6a96, 0x1a71, 0x0a50, 0x3a33, 0x2a12,
123 0xdbfd, 0xcbdc, 0xfbbf, 0xeb9e, 0x9b79, 0x8b58, 0xbb3b, 0xab1a,
124 0x6ca6, 0x7c87, 0x4ce4, 0x5cc5, 0x2c22, 0x3c03, 0x0c60, 0x1c41,
125 0xedae, 0xfd8f, 0xcdec, 0xddcd, 0xad2a, 0xbd0b, 0x8d68, 0x9d49,
126 0x7e97, 0x6eb6, 0x5ed5, 0x4ef4, 0x3e13, 0x2e32, 0x1e51, 0x0e70,
127 0xff9f, 0xefbe, 0xdfdd, 0xcffc, 0xbf1b, 0xaf3a, 0x9f59, 0x8f78,
128 0x9188, 0x81a9, 0xb1ca, 0xa1eb, 0xd10c, 0xc12d, 0xf14e, 0xe16f,
129 0x1080, 0x00a1, 0x30c2, 0x20e3, 0x5004, 0x4025, 0x7046, 0x6067,
130 0x83b9, 0x9398, 0xa3fb, 0xb3da, 0xc33d, 0xd31c, 0xe37f, 0xf35e,
131 0x02b1, 0x1290, 0x22f3, 0x32d2, 0x4235, 0x5214, 0x6277, 0x7256,
132 0xb5ea, 0xa5cb, 0x95a8, 0x8589, 0xf56e, 0xe54f, 0xd52c, 0xc50d,
133 0x34e2, 0x24c3, 0x14a0, 0x0481, 0x7466, 0x6447, 0x5424, 0x4405,
134 0xa7db, 0xb7fa, 0x8799, 0x97b8, 0xe75f, 0xf77e, 0xc71d, 0xd73c,
135 0x26d3, 0x36f2, 0x0691, 0x16b0, 0x6657, 0x7676, 0x4615, 0x5634,
136 0xd94c, 0xc96d, 0xf90e, 0xe92f, 0x99c8, 0x89e9, 0xb98a, 0xa9ab,
137 0x5844, 0x4865, 0x7806, 0x6827, 0x18c0, 0x08e1, 0x3882, 0x28a3,
138 0xcb7d, 0xdb5c, 0xeb3f, 0xfb1e, 0x8bf9, 0x9bd8, 0xabbb, 0xbb9a,
139 0x4a75, 0x5a54, 0x6a37, 0x7a16, 0x0af1, 0x1ad0, 0x2ab3, 0x3a92,
140 0xfd2e, 0xed0f, 0xdd6c, 0xcd4d, 0xbdaa, 0xad8b, 0x9de8, 0x8dc9,
141 0x7c26, 0x6c07, 0x5c64, 0x4c45, 0x3ca2, 0x2c83, 0x1ce0, 0x0cc1,
142 0xef1f, 0xff3e, 0xcf5d, 0xdf7c, 0xaf9b, 0xbfba, 0x8fd9, 0x9ff8,
143 0x6e17, 0x7e36, 0x4e55, 0x5e74, 0x2e93, 0x3eb2, 0x0ed1, 0x1ef0,
144];
145
146#[must_use]
148fn crc16(data: &[u8]) -> u16 {
149 let mut crc: u16 = 0;
150 for &byte in data {
151 let at = ((crc >> 8) ^ u16::from(byte)) & 0xff;
152 crc = (crc << 8) ^ CRC16[at as usize];
153 }
154 crc
155}
156
157#[must_use]
167pub fn key_slot(key: &[u8]) -> u16 {
168 let tagged = match key.iter().position(|&b| b == b'{') {
169 Some(open) => match key[open + 1..].iter().position(|&b| b == b'}') {
170 Some(0) | None => key,
172 Some(len) => &key[open + 1..open + 1 + len],
173 },
174 None => key,
175 };
176 crc16(tagged) % SLOTS as u16
177}
178
179pub(crate) const FLAG_MASTER: u16 = 1;
187pub(crate) const FLAG_SLAVE: u16 = 2;
188pub(crate) const FLAG_PFAIL: u16 = 4;
189pub(crate) const FLAG_FAIL: u16 = 8;
190pub(crate) const FLAG_MYSELF: u16 = 16;
191pub(crate) const FLAG_HANDSHAKE: u16 = 32;
192pub(crate) const FLAG_NOADDR: u16 = 64;
193pub(crate) const FLAG_MEET: u16 = 128;
194pub(crate) const FLAG_MIGRATE_TO: u16 = 256;
195pub(crate) const FLAG_NOFAILOVER: u16 = 512;
196pub(crate) const FLAG_EXTENSIONS: u16 = 1024;
197
198#[derive(Clone)]
200struct Node {
201 id: String,
203 host: String,
206 port: u16,
208 bus: u16,
212 shard: String,
214 epoch: u64,
216 flags: u16,
218 master: Option<u16>,
220 ping_sent: u64,
223 pong_recv: u64,
225 data_recv: u64,
229 fail_time: u64,
231 offset: u64,
233 linked: bool,
235 voted_time: u64,
239 reports: Vec<(String, u64)>,
242}
243
244impl Node {
245 fn new(id: String, host: String, port: u16, bus: u16, flags: u16, now: u64) -> Node {
247 Node {
248 id,
249 host,
250 port,
251 bus,
252 shard: String::from_utf8_lossy(&new_id()).into_owned(),
253 epoch: 0,
254 flags,
255 master: None,
256 ping_sent: 0,
257 pong_recv: 0,
262 data_recv: now,
265 fail_time: 0,
266 offset: 0,
267 linked: false,
268 voted_time: 0,
269 reports: Vec::new(),
270 }
271 }
272
273 fn is_master(&self) -> bool {
276 self.flags & FLAG_SLAVE == 0
277 }
278
279 fn down(&self) -> bool {
281 self.flags & (FLAG_PFAIL | FLAG_FAIL) != 0
282 }
283
284 fn flag_names(&self, into: &mut String) {
287 const NAMES: [(u16, &str); 8] = [
288 (FLAG_MYSELF, "myself"),
289 (FLAG_MASTER, "master"),
290 (FLAG_SLAVE, "slave"),
291 (FLAG_PFAIL, "fail?"),
292 (FLAG_FAIL, "fail"),
293 (FLAG_HANDSHAKE, "handshake"),
294 (FLAG_NOADDR, "noaddr"),
295 (FLAG_NOFAILOVER, "nofailover"),
296 ];
297 let mut first = true;
298 for (bit, name) in NAMES {
299 if self.flags & bit == 0 {
300 continue;
301 }
302 if !first {
303 into.push(',');
304 }
305 into.push_str(name);
306 first = false;
307 }
308 if first {
309 into.push_str("noflags");
310 }
311 }
312
313 fn address(&self, into: &mut String) {
315 let _ = write!(into, "{}:{}@{}", self.host, self.port, self.bus);
316 }
317
318 fn address_on_disk(&self, into: &mut String) {
323 self.address(into);
324 let _ = write!(into, ",,tls-port=0,shard-id={}", self.shard);
325 }
326}
327
328struct Map {
335 nodes: Vec<Node>,
337 owner: Vec<Option<u16>>,
339 migrating: Vec<Option<u16>>,
341 importing: Vec<Option<u16>>,
343}
344
345impl Map {
346 fn new(me: Node) -> Map {
348 Map {
349 nodes: vec![me],
350 owner: vec![None; SLOTS],
351 migrating: vec![None; SLOTS],
352 importing: vec![None; SLOTS],
353 }
354 }
355
356 fn find(&self, id: &[u8]) -> Option<u16> {
358 self.nodes
359 .iter()
360 .position(|n| n.id.as_bytes() == id)
361 .map(|at| at as u16)
362 }
363
364 fn forget(&mut self, at: u16) {
373 self.nodes.remove(usize::from(at));
374 let shift = |slot: &mut Option<u16>| match *slot {
375 Some(node) if node == at => *slot = None,
376 Some(node) if node > at => *slot = Some(node - 1),
377 _ => {}
378 };
379 for slot in 0..SLOTS {
380 shift(&mut self.owner[slot]);
381 shift(&mut self.migrating[slot]);
382 shift(&mut self.importing[slot]);
383 }
384 for node in &mut self.nodes {
385 shift(&mut node.master);
386 }
387 }
388
389 fn voters(&self) -> usize {
392 let mut seen = vec![false; self.nodes.len()];
393 for owner in self.owner.iter().flatten() {
394 seen[*owner as usize] = true;
395 }
396 seen.iter().filter(|s| **s).count()
397 }
398
399 fn mine(&self, slot: u16) -> bool {
401 self.owner[slot as usize] == Some(0)
402 }
403
404 fn assigned(&self) -> usize {
406 self.owner.iter().filter(|o| o.is_some()).count()
407 }
408
409 fn size(&self) -> usize {
412 let mut seen = vec![false; self.nodes.len()];
413 for owner in self.owner.iter().flatten() {
414 seen[*owner as usize] = true;
415 }
416 seen.iter().filter(|s| **s).count()
417 }
418
419 fn runs(&self, node: u16) -> Vec<(u16, u16)> {
421 let mut runs: Vec<(u16, u16)> = Vec::new();
422 for slot in 0..SLOTS as u16 {
423 if self.owner[slot as usize] != Some(node) {
424 continue;
425 }
426 match runs.last_mut() {
427 Some(last) if last.1 + 1 == slot => last.1 = slot,
428 _ => runs.push((slot, slot)),
429 }
430 }
431 runs
432 }
433}
434
435pub(crate) struct Cluster {
440 on: bool,
445 map: Lock<Map>,
447 epoch: AtomicU64,
449 covered_at: AtomicU64,
452 booted_at: AtomicU64,
455 was_down: AtomicBool,
458 full_coverage: AtomicBool,
461 reads_when_down: AtomicBool,
470 file: Lock<String>,
472 bus: bus::Bus,
475 vote: bus::Vote,
477 manual: bus::Manual,
479 asm: asm::Asm,
481}
482
483impl Default for Cluster {
484 fn default() -> Cluster {
485 Cluster {
486 on: false,
487 map: Lock::new(Map {
488 nodes: Vec::new(),
489 owner: Vec::new(),
490 migrating: Vec::new(),
491 importing: Vec::new(),
492 }),
493 epoch: AtomicU64::new(0),
494 covered_at: AtomicU64::new(0),
495 booted_at: AtomicU64::new(0),
496 was_down: AtomicBool::new(false),
497 full_coverage: AtomicBool::new(true),
498 reads_when_down: AtomicBool::new(false),
499 file: Lock::new(String::new()),
500 bus: bus::Bus::default(),
501 vote: bus::Vote::default(),
502 manual: bus::Manual::default(),
503 asm: asm::Asm::default(),
504 }
505 }
506}
507
508impl Server {
509 #[must_use]
512 pub fn cluster_enabled(&self) -> bool {
513 self.cluster.on
514 }
515
516 pub fn enable_cluster(&mut self, file: &str, port: u16) {
522 self.cluster.on = true;
523 self.cluster.booted_at.store(self.now_ms(), Relaxed);
524 let now = self.now_ms();
525 let me = yo_alloc::allow(|| {
526 Node::new(
527 String::from_utf8_lossy(&new_id()).into_owned(),
528 String::new(),
529 port,
530 port + BUS_OFFSET,
531 FLAG_MYSELF | FLAG_MASTER,
532 now,
533 )
534 });
535 let path = yo_alloc::allow(|| {
541 if file.is_empty() {
542 String::new()
543 } else {
544 self.dir().join(file).to_string_lossy().into_owned()
545 }
546 });
547 yo_alloc::allow(|| {
548 *self.cluster.map.lock() = Map::new(me);
549 *self.cluster.file.lock() = path;
550 *self.cluster.bus.secret.lock() = String::from_utf8_lossy(&new_id()).into_owned();
556 });
557 if let Err(e) = self.reload_cluster() {
562 eprintln!("cluster config file could not be read: {e}");
563 }
564 self.recount_coverage();
565 }
566
567 pub(crate) fn cluster_secret(&self) -> String {
577 let held = self.cluster.bus.secret.lock();
578 yo_alloc::allow(|| held.clone())
579 }
580
581 pub(crate) fn cluster_full_coverage(&self) -> bool {
583 self.cluster.full_coverage.load(Relaxed)
584 }
585
586 pub(crate) fn cluster_reads_when_down(&self) -> bool {
588 self.cluster.reads_when_down.load(Relaxed)
589 }
590
591 pub(crate) fn set_cluster_coverage(&self, full: bool, reads_when_down: bool) {
594 self.cluster.full_coverage.store(full, Relaxed);
595 self.cluster.reads_when_down.store(reads_when_down, Relaxed);
596 self.recount_coverage();
597 }
598
599 pub(crate) fn cluster_file(&self) -> String {
601 let file = self.cluster.file.lock();
602 yo_alloc::allow(|| file.clone())
603 }
604
605 pub(crate) fn cluster_id(&self) -> String {
607 let map = self.cluster.map.lock();
608 yo_alloc::allow(|| map.nodes.first().map_or_else(String::new, |n| n.id.clone()))
609 }
610
611 pub(crate) fn cluster_up(&self) -> bool {
618 let at = self.cluster.covered_at.load(Relaxed);
619 if at == 0 {
620 return false;
621 }
622 let (since, wait) = if self.cluster.was_down.load(Relaxed) {
623 (at, REJOIN_DELAY_MS)
624 } else {
625 (self.cluster.booted_at.load(Relaxed), WRITABLE_DELAY_MS)
626 };
627 self.now_ms().saturating_sub(since) >= wait
628 }
629
630 fn recount_coverage(&self) {
636 let covered = {
637 let map = self.cluster.map.lock();
638 let assigned = map.assigned();
639 if self.cluster_full_coverage() {
640 assigned == SLOTS
641 } else {
642 assigned > 0
643 }
644 };
645 if covered {
646 let _ =
647 self.cluster
648 .covered_at
649 .compare_exchange(0, self.now_ms().max(1), Relaxed, Relaxed);
650 } else {
651 self.cluster.covered_at.store(0, Relaxed);
652 self.cluster.was_down.store(true, Relaxed);
653 }
654 }
655}
656
657fn new_id() -> [u8; ID_LEN] {
659 const HEX: &[u8; 16] = b"0123456789abcdef";
660 let mut raw = [0u8; ID_LEN / 2];
661 yo_common::entropy::fill(&mut raw);
662 let mut id = [0u8; ID_LEN];
663 for (i, byte) in raw.iter().enumerate() {
664 id[i * 2] = HEX[usize::from(byte >> 4)];
665 id[i * 2 + 1] = HEX[usize::from(byte & 15)];
666 }
667 id
668}
669
670pub(super) fn asks(spec: &Spec) -> bool {
680 spec.flags.contains(&"asking")
681}
682
683pub(super) fn gate(
702 server: &Server,
703 db: usize,
704 asking: bool,
705 spec: &Spec,
706 args: Args<'_>,
707) -> Option<Error> {
708 if !keyspec::takes_keys(spec, args, 0) {
711 return None;
712 }
713 let mut slot: Option<u16> = None;
714 let mut crossed = false;
715 let mut keys = 0usize;
716 let mut present = 0usize;
717 let mut missing = 0usize;
718 let (owner, migrating, importing, here) = {
719 let map = server.cluster.map.lock();
720 keyspec::find(spec, args, 0, &mut |run| {
722 for i in 0..run.count {
723 let at = run.first + i * run.step;
724 if at >= args.len() {
725 continue;
726 }
727 let this = key_slot(args.get(at));
728 keys += 1;
729 match slot {
730 None => slot = Some(this),
731 Some(first) if first != this => crossed = true,
732 Some(_) => {}
733 }
734 }
735 });
736 let at = usize::from(slot?);
737 (
738 map.owner[at],
739 map.migrating[at].map(|to| node_at(&map, to)),
740 map.importing[at].is_some(),
741 map.owner[at] == Some(0),
742 )
743 };
744 let slot = slot?;
745 let Some(owner) = owner else {
748 return Some(Error::new(
749 Code::Invalid,
750 "CLUSTERDOWN Hash slot not served",
751 ));
752 };
753 if crossed {
754 return Some(Error::new(
755 Code::Invalid,
756 "CROSSSLOT Keys in request don't hash to the same slot",
757 ));
758 }
759 if !server.cluster_up() {
764 if !server.cluster.reads_when_down.load(Relaxed) {
765 return Some(Error::new(Code::Invalid, "CLUSTERDOWN The cluster is down"));
766 }
767 if spec.flags.contains(&"write") {
768 return Some(Error::new(
769 Code::Invalid,
770 "CLUSTERDOWN The cluster is down and only accepts read commands",
771 ));
772 }
773 }
774 if migrating.is_some() || importing {
778 let held = &server.dbs[db];
779 keyspec::find(spec, args, 0, &mut |run| {
780 for i in 0..run.count {
781 let at = run.first + i * run.step;
782 if at >= args.len() {
783 continue;
784 }
785 let key = args.get(at);
786 let mut stripe = held.hold(key);
787 if stripe.exists(key) {
788 present += 1;
789 } else {
790 missing += 1;
791 }
792 }
793 });
794 }
795 if let Some(to) = migrating
796 && missing > 0
797 {
798 if present > 0 {
801 return Some(Error::new(
802 Code::Invalid,
803 "TRYAGAIN Multiple keys request during rehashing of slot",
804 ));
805 }
806 return Some(redirect("ASK", slot, &to));
807 }
808 if importing && asking {
809 if keys > 1 && missing > 0 {
810 return Some(Error::new(
811 Code::Invalid,
812 "TRYAGAIN Multiple keys request during rehashing of slot",
813 ));
814 }
815 return None;
816 }
817 if here {
818 return None;
819 }
820 let (host, port) = {
821 let map = server.cluster.map.lock();
822 node_at(&map, owner)
823 };
824 Some(redirect("MOVED", slot, &(host, port)))
825}
826
827fn node_at(map: &Map, at: u16) -> (String, u16) {
829 let node = &map.nodes[at as usize];
830 (yo_alloc::allow(|| node.host.clone()), node.port)
831}
832
833fn redirect(word: &str, slot: u16, node: &(String, u16)) -> Error {
839 let host = if node.0.is_empty() {
840 "127.0.0.1"
841 } else {
842 node.0.as_str()
843 };
844 Error::fmt(
845 Code::Invalid,
846 format_args!("{word} {slot} {host}:{}", node.1),
847 )
848}
849
850pub(super) fn execute(
854 server: &Server,
855 session: &mut Session,
856 args: Args<'_>,
857 out: &mut Out,
858) -> Result<()> {
859 let session_db = session.db;
860 let sub = args.get(1);
861 if !server.cluster_enabled() {
865 return match arity_of(sub) {
866 Some(n) if !arity_ok(n, args.len()) => Err(wrong_sub_arity(sub)),
867 Some(_) => Err(disabled()),
868 None => Err(args::unknown_subcommand(sub, "CLUSTER")),
869 };
870 }
871 let Some(n) = arity_of(sub) else {
872 return Err(args::unknown_subcommand(sub, "CLUSTER"));
873 };
874 if !arity_ok(n, args.len()) {
875 return Err(wrong_sub_arity(sub));
876 }
877 match () {
878 () if args::is(sub, b"myid") => out.bulk(server.cluster_id().as_bytes()),
879 () if args::is(sub, b"myshardid") => {
880 let map = server.cluster.map.lock();
881 out.bulk(map.nodes[0].shard.as_bytes());
882 }
883 () if args::is(sub, b"keyslot") => out.int(i64::from(key_slot(args.get(2)))),
884 () if args::is(sub, b"info") => info(server, out),
885 () if args::is(sub, b"nodes") => nodes(server, out),
886 () if args::is(sub, b"slots") => reply_slots(server, out),
887 () if args::is(sub, b"shards") => shards(server, out),
888 () if args::is(sub, b"links") => server.cluster_links(out),
889 () if args::is(sub, b"slaves") || args::is(sub, b"replicas") => {
890 replicas(server, args.get(2), out)?;
891 }
892 () if args::is(sub, b"count-failure-reports") => {
893 let map = server.cluster.map.lock();
894 let Some(at) = map.find(args.get(2)) else {
895 return Err(unknown_node(args.get(2)));
896 };
897 out.int(map.nodes[usize::from(at)].reports.len() as i64);
898 }
899 () if args::is(sub, b"countkeysinslot") => count_keys(server, session_db, args, out)?,
900 () if args::is(sub, b"getkeysinslot") => get_keys(server, session_db, args, out)?,
901 () if args::is(sub, b"addslots") => {
902 add_or_del(server, args, true, false)?;
903 out.ok();
904 }
905 () if args::is(sub, b"delslots") => {
906 add_or_del(server, args, false, false)?;
907 out.ok();
908 }
909 () if args::is(sub, b"addslotsrange") => {
910 add_or_del(server, args, true, true)?;
911 out.ok();
912 }
913 () if args::is(sub, b"delslotsrange") => {
914 add_or_del(server, args, false, true)?;
915 out.ok();
916 }
917 () if args::is(sub, b"setslot") => setslot(server, session_db, args, out)?,
918 () if args::is(sub, b"flushslots") => flushslots(server, out)?,
919 () if args::is(sub, b"bumpepoch") => bumpepoch(server, out)?,
920 () if args::is(sub, b"set-config-epoch") => set_config_epoch(server, args, out)?,
921 () if args::is(sub, b"reset") => {
922 if args.len() > 3 {
923 return Err(sub_syntax(sub));
924 }
925 reset(server, args, out)?;
926 }
927 () if args::is(sub, b"slot-stats") => slot_stats(server, session_db, args, out)?,
928 () if args::is(sub, b"migration") => migration(server, args, out)?,
929 () if args::is(sub, b"syncslots") => syncslots(server, session, args, out)?,
930 () if args::is(sub, b"saveconfig") => {
931 save(server)?;
932 out.ok();
933 }
934 () if args::is(sub, b"forget") => {
935 forget(server, args.get(2))?;
936 out.ok();
937 }
938 () if args::is(sub, b"replicate") => {
939 replicate(server, session_db, args.get(2))?;
940 out.ok();
941 }
942 () if args::is(sub, b"failover") => {
943 if args.len() > 3 {
944 return Err(sub_syntax(sub));
945 }
946 let takeover = args.len() == 3 && args::is(args.get(2), b"takeover");
950 let force = takeover || (args.len() == 3 && args::is(args.get(2), b"force"));
951 if args.len() == 3 && !force {
952 return Err(args::syntax());
953 }
954 let Some(shared) = server.myself() else {
955 return Err(Error::new(
956 Code::Invalid,
957 "CLUSTER FAILOVER is not available on an embedded server",
958 ));
959 };
960 bus::manual_failover(&shared, force, takeover)?;
961 out.ok();
962 }
963 () if args::is(sub, b"meet") => {
964 if args.len() > 5 {
965 return Err(sub_syntax(sub));
966 }
967 let (host, port, bus) = meet(&args)?;
968 server.cluster_meet(&host, port, bus);
969 out.ok();
970 }
971 () if args::is(sub, b"help") => help(out),
972 _ => return Err(args::unknown_subcommand(sub, "CLUSTER")),
973 }
974 Ok(())
975}
976
977pub(super) fn disabled() -> Error {
979 Error::new(Code::Invalid, "This instance has cluster support disabled")
980}
981
982fn unknown_node(id: &[u8]) -> Error {
984 Error::fmt(
985 Code::Invalid,
986 format_args!("Unknown node {}", String::from_utf8_lossy(id)),
987 )
988}
989
990fn dont_know(id: &[u8]) -> Error {
993 Error::fmt(
994 Code::Invalid,
995 format_args!("I don't know about node {}", String::from_utf8_lossy(id)),
996 )
997}
998
999fn arity_of(sub: &[u8]) -> Option<i32> {
1006 const TABLE: &[(&str, i32)] = &[
1007 ("addslots", -3),
1008 ("addslotsrange", -4),
1009 ("bumpepoch", 2),
1010 ("count-failure-reports", 3),
1011 ("countkeysinslot", 3),
1012 ("delslots", -3),
1013 ("delslotsrange", -4),
1014 ("failover", -2),
1015 ("flushslots", 2),
1016 ("forget", 3),
1017 ("getkeysinslot", 4),
1018 ("help", 2),
1019 ("info", 2),
1020 ("keyslot", 3),
1021 ("links", 2),
1022 ("meet", -4),
1023 ("migration", -4),
1024 ("myid", 2),
1025 ("myshardid", 2),
1026 ("nodes", 2),
1027 ("replicas", 3),
1028 ("replicate", 3),
1029 ("reset", -2),
1030 ("saveconfig", 2),
1031 ("set-config-epoch", 3),
1032 ("setslot", -4),
1033 ("shards", 2),
1034 ("slaves", 3),
1035 ("slot-stats", -4),
1036 ("slots", 2),
1037 ("syncslots", -3),
1038 ];
1039 TABLE
1040 .iter()
1041 .find(|(name, _)| args::is(sub, name.as_bytes()))
1042 .map(|(_, arity)| *arity)
1043}
1044
1045fn arity_ok(arity: i32, len: usize) -> bool {
1047 let len = len as i32;
1048 if arity >= 0 {
1049 len == arity
1050 } else {
1051 len >= -arity
1052 }
1053}
1054
1055fn sub_syntax(sub: &[u8]) -> Error {
1063 Error::fmt(
1064 Code::Unsupported,
1065 format_args!(
1066 "unknown subcommand or wrong number of arguments for '{}'. Try CLUSTER HELP.",
1067 String::from_utf8_lossy(sub)
1068 ),
1069 )
1070}
1071
1072fn wrong_sub_arity(sub: &[u8]) -> Error {
1074 Error::fmt(
1075 Code::Invalid,
1076 format_args!(
1077 "wrong number of arguments for 'cluster|{}' command",
1078 String::from_utf8_lossy(sub).to_lowercase()
1079 ),
1080 )
1081}
1082
1083fn info(server: &Server, out: &mut Out) {
1088 let (assigned, size, known, my_epoch) = {
1089 let map = server.cluster.map.lock();
1090 (
1091 map.assigned(),
1092 map.size(),
1093 map.nodes.len(),
1094 map.nodes[0].epoch,
1095 )
1096 };
1097 let state = if server.cluster_up() { "ok" } else { "fail" };
1098 let text = yo_alloc::allow(|| {
1099 let mut s = String::with_capacity(512);
1100 let _ = write!(
1101 s,
1102 "cluster_state:{state}\r\ncluster_slots_assigned:{assigned}\r\n\
1103 cluster_slots_ok:{assigned}\r\ncluster_slots_pfail:0\r\ncluster_slots_fail:0\r\n\
1104 cluster_known_nodes:{known}\r\ncluster_size:{size}\r\n\
1105 cluster_current_epoch:{}\r\ncluster_my_epoch:{my_epoch}\r\n\
1106 cluster_stats_messages_sent:0\r\ncluster_stats_messages_received:0\r\n\
1107 total_cluster_links_buffer_limit_exceeded:0\r\n\
1108 cluster_slot_migration_active_tasks:0\r\n\
1109 cluster_slot_migration_active_trim_running:0\r\n\
1110 cluster_slot_migration_active_trim_current_job_keys:0\r\n\
1111 cluster_slot_migration_active_trim_current_job_trimmed:0\r\n\
1112 cluster_slot_migration_stats_active_trim_started:0\r\n\
1113 cluster_slot_migration_stats_active_trim_completed:0\r\n\
1114 cluster_slot_migration_stats_active_trim_cancelled:0\r\n",
1115 server.cluster.epoch.load(Relaxed),
1116 );
1117 s
1118 });
1119 out.verbatim(b"txt", text.as_bytes());
1120}
1121
1122fn nodes(server: &Server, out: &mut Out) {
1124 let text = yo_alloc::allow(|| lines(server, false));
1125 out.verbatim(b"txt", text.as_bytes());
1126}
1127
1128fn lines(server: &Server, on_disk: bool) -> String {
1134 let map = server.cluster.map.lock();
1135 let mut s = String::with_capacity(256);
1136 for at in 0..map.nodes.len() as u16 {
1137 describe(&map, at, on_disk, &mut s);
1138 s.push('\n');
1139 }
1140 s
1141}
1142
1143fn describe(map: &Map, at: u16, on_disk: bool, s: &mut String) {
1146 let node = &map.nodes[usize::from(at)];
1147 s.push_str(&node.id);
1148 s.push(' ');
1149 if on_disk {
1150 node.address_on_disk(s);
1151 } else {
1152 node.address(s);
1153 }
1154 s.push(' ');
1155 node.flag_names(s);
1156 s.push(' ');
1157 match node.master.and_then(|m| map.nodes.get(usize::from(m))) {
1158 Some(master) => s.push_str(&master.id),
1159 None => s.push('-'),
1160 }
1161 let epoch = match node.master.and_then(|m| map.nodes.get(usize::from(m))) {
1164 Some(master) => master.epoch,
1165 None => node.epoch,
1166 };
1167 let link = if node.linked || at == 0 {
1168 "connected"
1169 } else {
1170 "disconnected"
1171 };
1172 let _ = write!(s, " {} {} {epoch} {link}", node.ping_sent, node.pong_recv);
1173 for (from, to) in map.runs(at) {
1174 if from == to {
1175 let _ = write!(s, " {from}");
1176 } else {
1177 let _ = write!(s, " {from}-{to}");
1178 }
1179 }
1180 if at == 0 {
1181 for slot in 0..SLOTS {
1182 if let Some(to) = map.migrating[slot] {
1183 let _ = write!(s, " [{slot}->-{}]", map.nodes[to as usize].id);
1184 }
1185 if let Some(from) = map.importing[slot] {
1186 let _ = write!(s, " [{slot}-<-{}]", map.nodes[from as usize].id);
1187 }
1188 }
1189 }
1190}
1191
1192fn reply_slots(server: &Server, out: &mut Out) {
1198 let mine = server.repl_offset();
1199 let map = server.cluster.map.lock();
1200 let at = out.len();
1201 let mut n = 0;
1202 let mut run: Option<(u16, u16)> = None;
1203 for slot in 0..=SLOTS as u16 {
1204 let owner = if slot as usize == SLOTS {
1205 None
1206 } else {
1207 map.owner[slot as usize]
1208 };
1209 match run {
1210 Some((node, _)) if owner == Some(node) => {}
1211 Some((node, from)) => {
1212 slot_run(&map, node, from, slot - 1, mine, out);
1213 n += 1;
1214 run = owner.map(|node| (node, slot));
1215 }
1216 None => run = owner.map(|node| (node, slot)),
1217 }
1218 }
1219 out.close_array(at, n);
1220}
1221
1222fn slot_run(map: &Map, node: u16, from: u16, to: u16, mine: u64, out: &mut Out) {
1230 let replicas: Vec<&Node> = map
1231 .nodes
1232 .iter()
1233 .enumerate()
1234 .filter(|(at, n)| {
1235 let offset = if *at == 0 { mine } else { n.offset };
1236 n.master == Some(node) && n.flags & FLAG_FAIL == 0 && offset != 0
1237 })
1238 .map(|(_, n)| n)
1239 .collect();
1240 out.array(3 + replicas.len());
1241 out.int(i64::from(from));
1242 out.int(i64::from(to));
1243 let held = &map.nodes[node as usize];
1244 for held in std::iter::once(held).chain(replicas.iter().copied()) {
1245 out.array(4);
1246 out.bulk(held.host.as_bytes());
1247 out.int(i64::from(held.port));
1248 out.bulk(held.id.as_bytes());
1249 out.array(0);
1250 }
1251}
1252
1253fn shards(server: &Server, out: &mut Out) {
1256 let map = server.cluster.map.lock();
1257 let at = out.len();
1258 let mut n = 0;
1259 let mut done: Vec<&str> = Vec::new();
1260 for node in 0..map.nodes.len() {
1261 let shard = map.nodes[node].shard.as_str();
1262 if done.contains(&shard) {
1263 continue;
1264 }
1265 done.push(shard);
1266 let members: Vec<usize> = (0..map.nodes.len())
1267 .filter(|other| map.nodes[*other].shard == shard)
1268 .collect();
1269 let runs: Vec<(u16, u16)> = members
1273 .iter()
1274 .flat_map(|member| map.runs(*member as u16))
1275 .collect();
1276 out.map(2);
1277 out.bulk(b"slots");
1278 out.array(runs.len() * 2);
1279 for (from, to) in runs {
1280 out.int(i64::from(from));
1281 out.int(i64::from(to));
1282 }
1283 out.bulk(b"nodes");
1284 out.array(members.len());
1285 for member in members {
1286 let held = &map.nodes[member];
1287 out.map(7);
1288 out.bulk(b"id");
1289 out.bulk(held.id.as_bytes());
1290 out.bulk(b"port");
1291 out.int(i64::from(held.port));
1292 out.bulk(b"ip");
1293 out.bulk(held.host.as_bytes());
1294 out.bulk(b"endpoint");
1295 out.bulk(held.host.as_bytes());
1296 out.bulk(b"role");
1297 out.bulk(if held.is_master() {
1298 b"master".as_slice()
1299 } else {
1300 b"replica".as_slice()
1301 });
1302 out.bulk(b"replication-offset");
1303 out.int(if member == 0 {
1304 server.repl_offset() as i64
1305 } else {
1306 held.offset as i64
1307 });
1308 out.bulk(b"health");
1309 out.bulk(match held.flags {
1310 f if f & FLAG_FAIL != 0 => b"fail".as_slice(),
1311 f if f & FLAG_PFAIL != 0 => b"loading".as_slice(),
1312 _ => b"online".as_slice(),
1313 });
1314 }
1315 n += 1;
1316 }
1317 out.close_array(at, n);
1318}
1319
1320fn help(out: &mut Out) {
1322 const LINES: &[&str] = &[
1323 "CLUSTER <subcommand> [<arg> [value] [opt] ...]. Subcommands are:",
1324 "COUNTKEYSINSLOT <slot>",
1325 " Return the number of keys in <slot>.",
1326 "GETKEYSINSLOT <slot> <count>",
1327 " Return key names stored by current node in a slot.",
1328 "INFO",
1329 " Return information about the cluster.",
1330 "KEYSLOT <key>",
1331 " Return the hash slot for <key>.",
1332 "MYID",
1333 " Return the node id.",
1334 "MYSHARDID",
1335 " Return the node's shard id.",
1336 "NODES",
1337 " Return cluster configuration seen by node. Output format:",
1338 " <id> <ip:port@bus-port[,hostname]> <flags> <master> <pings> <pongs> <epoch> <link> <slot> ...",
1339 "REPLICAS <node-id>",
1340 " Return <node-id> replicas.",
1341 "SLOTS",
1342 " Return information about slots range mappings. Each range is made of:",
1343 " start, end, master and replicas IP addresses, ports and ids",
1344 "SLOT-STATS",
1345 " Return an array of slot usage statistics for slots assigned to the current node.",
1346 "SHARDS",
1347 " Return information about slot range mappings and the nodes associated with them.",
1348 "ADDSLOTS <slot> [<slot> ...]",
1349 " Assign slots to current node.",
1350 "ADDSLOTSRANGE <start slot> <end slot> [<start slot> <end slot> ...]",
1351 " Assign slots which are between <start-slot> and <end-slot> to current node.",
1352 "BUMPEPOCH",
1353 " Advance the cluster config epoch.",
1354 "COUNT-FAILURE-REPORTS <node-id>",
1355 " Return number of failure reports for <node-id>.",
1356 "DELSLOTS <slot> [<slot> ...]",
1357 " Delete slots information from current node.",
1358 "DELSLOTSRANGE <start slot> <end slot> [<start slot> <end slot> ...]",
1359 " Delete slots information which are between <start-slot> and <end-slot> from current node.",
1360 "FAILOVER [FORCE|TAKEOVER]",
1361 " Promote current replica node to being a master.",
1362 "FORGET <node-id>",
1363 " Remove a node from the cluster.",
1364 "FLUSHSLOTS",
1365 " Delete current node own slots information.",
1366 "MEET <ip> <port> [<bus-port>]",
1367 " Connect nodes into a working cluster.",
1368 "REPLICATE <node-id>",
1369 " Configure current node as replica to <node-id>.",
1370 "RESET [HARD|SOFT]",
1371 " Reset current node (default: soft).",
1372 "SET-CONFIG-EPOCH <epoch>",
1373 " Set config epoch of current node.",
1374 "SETSLOT <slot> (IMPORTING <node-id>|MIGRATING <node-id>|STABLE|NODE <node-id>)",
1375 " Set slot state.",
1376 "SAVECONFIG",
1377 " Force saving cluster configuration on disk.",
1378 "LINKS",
1379 " Return information about all network links between this node and its peers.",
1380 " Output format is an array where each array element is a map containing attributes of a link",
1381 "MIGRATION IMPORT <start-slot end-slot [start-slot end-slot ...]> |",
1382 " STATUS [ID <task-id> | ALL] | CANCEL [ID <task-id> | ALL]",
1383 " Start, monitor and cancel slot migration.",
1384 "HELP",
1385 " Print this help.",
1386 ];
1387 out.array(LINES.len());
1388 for line in LINES {
1389 out.simple(line.as_bytes());
1390 }
1391}
1392
1393fn slot_arg(args: &Args<'_>, at: usize) -> Result<u16> {
1402 args.int(at)
1403 .ok()
1404 .and_then(|n| u16::try_from(n).ok())
1405 .filter(|s| usize::from(*s) < SLOTS)
1406 .ok_or_else(|| Error::new(Code::Invalid, "Invalid or out of range slot"))
1407}
1408
1409fn add_or_del(server: &Server, args: Args<'_>, add: bool, ranged: bool) -> Result<()> {
1416 let stride = if ranged { 2 } else { 1 };
1417 if ranged && !(args.len() - 2).is_multiple_of(2) {
1418 return Err(wrong_sub_arity(args.get(1)));
1419 }
1420 let mut wanted = Vec::new();
1421 let mut at = 2;
1422 while at < args.len() {
1423 let from = slot_arg(&args, at)?;
1424 let to = if ranged {
1425 slot_arg(&args, at + 1)?
1426 } else {
1427 from
1428 };
1429 if from > to {
1430 return Err(Error::fmt(
1431 Code::Invalid,
1432 format_args!("start slot number {from} is greater than end slot number {to}"),
1433 ));
1434 }
1435 for slot in from..=to {
1436 wanted.push(slot);
1437 }
1438 at += stride;
1439 }
1440 {
1441 let mut map = server.cluster.map.lock();
1442 let mut seen = vec![false; SLOTS];
1443 for slot in &wanted {
1444 let slot = usize::from(*slot);
1445 if seen[slot] {
1446 return Err(Error::fmt(
1447 Code::Invalid,
1448 format_args!("Slot {slot} specified multiple times"),
1449 ));
1450 }
1451 seen[slot] = true;
1452 let busy = map.owner[slot].is_some();
1453 if add && busy {
1454 return Err(Error::fmt(
1455 Code::Invalid,
1456 format_args!("Slot {slot} is already busy"),
1457 ));
1458 }
1459 if !add && !busy {
1460 return Err(Error::fmt(
1461 Code::Invalid,
1462 format_args!("Slot {slot} is already unassigned"),
1463 ));
1464 }
1465 }
1466 for slot in &wanted {
1467 let slot = usize::from(*slot);
1468 map.owner[slot] = if add { Some(0) } else { None };
1469 map.migrating[slot] = None;
1470 map.importing[slot] = None;
1471 }
1472 }
1473 server.recount_coverage();
1474 save(server)
1475}
1476
1477fn meet(args: &Args<'_>) -> Result<(String, u16, u16)> {
1483 let host = String::from_utf8_lossy(args.get(2));
1484 let typed = String::from_utf8_lossy(args.get(3));
1485 let port = args.int(3).map_err(|_| {
1486 Error::fmt(
1487 Code::Invalid,
1488 format_args!("Invalid base port specified: {typed}"),
1489 )
1490 })?;
1491 let bus = match args.opt(4) {
1492 None => port + i64::from(BUS_OFFSET),
1493 Some(word) => args.int(4).map_err(|_| {
1494 Error::fmt(
1495 Code::Invalid,
1496 format_args!(
1497 "Invalid bus port specified: {}",
1498 String::from_utf8_lossy(word)
1499 ),
1500 )
1501 })?,
1502 };
1503 if !(1..=65535).contains(&port) || !(0..=65535).contains(&bus) {
1504 return Err(Error::fmt(
1505 Code::Invalid,
1506 format_args!("Invalid node address specified: {host}:{typed}"),
1507 ));
1508 }
1509 let bus = if bus == 0 {
1510 port + i64::from(BUS_OFFSET)
1511 } else {
1512 bus
1513 };
1514 let host = yo_alloc::allow(|| host.into_owned());
1515 Ok((host, port as u16, bus as u16))
1516}
1517
1518fn replicas(server: &Server, id: &[u8], out: &mut Out) -> Result<()> {
1520 let map = server.cluster.map.lock();
1521 let Some(at) = map.find(id) else {
1522 return Err(unknown_node(id));
1523 };
1524 if !map.nodes[usize::from(at)].is_master() {
1525 return Err(Error::new(
1526 Code::Invalid,
1527 "The specified node is not a master",
1528 ));
1529 }
1530 let start = out.len();
1531 let mut n = 0;
1532 for other in 0..map.nodes.len() as u16 {
1533 if map.nodes[usize::from(other)].master != Some(at) {
1534 continue;
1535 }
1536 let line = yo_alloc::allow(|| {
1537 let mut s = String::with_capacity(256);
1538 describe(&map, other, false, &mut s);
1539 s
1540 });
1541 out.bulk(line.as_bytes());
1542 n += 1;
1543 }
1544 out.close_array(start, n);
1545 Ok(())
1546}
1547
1548fn forget(server: &Server, id: &[u8]) -> Result<()> {
1555 let at = {
1556 let map = server.cluster.map.lock();
1557 match map.find(id) {
1558 Some(0) => {
1559 return Err(Error::new(
1560 Code::Invalid,
1561 "I tried hard but I can't forget myself...",
1562 ));
1563 }
1564 Some(at) if map.nodes[0].master == Some(at) => {
1565 return Err(Error::new(Code::Invalid, "Can't forget my master!"));
1566 }
1567 Some(at) => at,
1568 None => {
1569 let name = String::from_utf8_lossy(id);
1570 if server.cluster_blacklisted(&name) {
1571 return Ok(());
1572 }
1573 return Err(unknown_node(id));
1574 }
1575 }
1576 };
1577 server.cluster_forget(at);
1578 Ok(())
1579}
1580
1581fn replicate(server: &Server, db: usize, id: &[u8]) -> Result<()> {
1587 let at = {
1588 let map = server.cluster.map.lock();
1589 match map.find(id) {
1590 None => return Err(unknown_node(id)),
1591 Some(0) => return Err(Error::new(Code::Invalid, "Can't replicate myself")),
1592 Some(at) if !map.nodes[usize::from(at)].is_master() => {
1593 return Err(Error::new(
1594 Code::Invalid,
1595 "I can only replicate a master, not a replica.",
1596 ));
1597 }
1598 Some(at) => {
1599 if map.nodes[0].is_master()
1600 && (!map.runs(0).is_empty() || !server.dbs[db].is_empty())
1601 {
1602 return Err(Error::new(
1603 Code::Invalid,
1604 "To set a master the node must be empty and without assigned slots.",
1605 ));
1606 }
1607 at
1608 }
1609 }
1610 };
1611 let Some(shared) = server.myself() else {
1612 return Err(Error::new(
1613 Code::Invalid,
1614 "CLUSTER REPLICATE is not available on an embedded server",
1615 ));
1616 };
1617 shared.cluster_replicate(at);
1618 Ok(())
1619}
1620
1621fn slot_stats(server: &Server, db: usize, args: Args<'_>, out: &mut Out) -> Result<()> {
1629 let sub = args.get(1);
1630 let mut wanted: Vec<u16> = Vec::new();
1631 let mut limit = SLOTS;
1632 let mut ascending = false;
1633 let ordered = args::is(args.get(2), b"orderby");
1634 if args::is(args.get(2), b"slotsrange") {
1635 if args.len() != 5 {
1636 return Err(sub_syntax(sub));
1637 }
1638 let from = slot_arg(&args, 3)?;
1639 let to = slot_arg(&args, 4)?;
1640 if from > to {
1641 return Err(Error::fmt(
1642 Code::Invalid,
1643 format_args!("Start slot number {from} is greater than end slot number {to}"),
1644 ));
1645 }
1646 wanted.extend(from..=to);
1647 } else if ordered {
1648 if !args::is(args.get(3), b"key-count") {
1649 return Err(Error::new(
1650 Code::Invalid,
1651 "Unrecognized sort metric for ORDERBY.",
1652 ));
1653 }
1654 let bad_limit = || {
1655 Error::new(
1656 Code::Invalid,
1657 "Limit has to lie in between 1 and 16384 (maximum number of slots).",
1658 )
1659 };
1660 let mut at = 4;
1661 while at < args.len() {
1662 let word = args.get(at);
1663 if args::is(word, b"limit") && at + 1 < args.len() {
1664 let n = args.int(at + 1).map_err(|_| bad_limit())?;
1665 if !(1..=SLOTS as i64).contains(&n) {
1666 return Err(bad_limit());
1667 }
1668 limit = n as usize;
1669 at += 2;
1670 } else if args::is(word, b"asc") {
1671 ascending = true;
1672 at += 1;
1673 } else if args::is(word, b"desc") {
1674 at += 1;
1675 } else {
1676 return Err(args::syntax());
1677 }
1678 }
1679 wanted.extend(0..SLOTS as u16);
1680 } else {
1681 return Err(sub_syntax(sub));
1682 }
1683 let mut counts = vec![0i64; SLOTS];
1684 server.dbs[db].keys(|key| counts[key_slot(key) as usize] += 1);
1685 let map = server.cluster.map.lock();
1686 wanted.retain(|slot| map.mine(*slot));
1687 drop(map);
1688 if ordered {
1689 if ascending {
1692 wanted.sort_by_key(|slot| (counts[*slot as usize], *slot));
1693 } else {
1694 wanted.sort_by_key(|slot| (-counts[*slot as usize], *slot));
1695 }
1696 wanted.truncate(limit);
1697 }
1698 out.array(wanted.len());
1699 for slot in wanted {
1700 out.array(2);
1701 out.int(i64::from(slot));
1702 out.map(1);
1703 out.bulk(b"key-count");
1704 out.int(counts[slot as usize]);
1705 }
1706 Ok(())
1707}
1708
1709fn migration(server: &Server, args: Args<'_>, out: &mut Out) -> Result<()> {
1717 let sub = args.get(1);
1718 let action = args.get(2);
1719 if args::is(action, b"status") || args::is(action, b"cancel") {
1720 let by_id = args::is(args.get(3), b"id");
1721 if by_id && args.len() != 5 {
1722 return Err(wrong_sub_arity(sub));
1723 }
1724 if !by_id && !args::is(args.get(3), b"all") {
1725 return Err(Error::new(Code::Invalid, "unknown argument"));
1726 }
1727 if !by_id && args.len() != 4 {
1728 return Err(wrong_sub_arity(sub));
1729 }
1730 let id = by_id.then(|| args.get(4));
1731 if args::is(action, b"status") {
1732 match id {
1733 Some(id) => server.cluster.asm.report_one(id, out),
1734 None => server.cluster.asm.report_all(out),
1735 }
1736 } else {
1737 out.int(server.cluster.asm.cancel(id, server.now_ms() as i64));
1738 server.asm_relax();
1741 }
1742 return Ok(());
1743 }
1744 if !args::is(action, b"import") {
1745 return Err(Error::new(Code::Invalid, "unknown argument"));
1746 }
1747 let ranges = slot_ranges(&args, 3)?;
1748 let (source, host, port) = import_source(server, &ranges)?;
1749 let Some(shared) = server.myself() else {
1754 return Err(Error::new(
1755 Code::Invalid,
1756 "slot migration needs a server with a cluster bus",
1757 ));
1758 };
1759 let id = server.asm_begin_import(source, ranges.clone())?;
1760 out.bulk(id.as_bytes());
1761 import::start(
1762 &shared,
1763 import::Job {
1764 id,
1765 host,
1766 port,
1767 slots: ranges,
1768 },
1769 );
1770 Ok(())
1771}
1772
1773fn import_source(server: &Server, ranges: &[(u16, u16)]) -> Result<(Vec<u8>, String, u16)> {
1780 let map = server.cluster.map.lock();
1781 if !map.nodes[0].is_master() {
1782 return Err(Error::new(
1783 Code::Invalid,
1784 "slot migration not allowed on replica.",
1785 ));
1786 }
1787 if (0..SLOTS).any(|at| map.migrating[at].is_some() || map.importing[at].is_some()) {
1790 return Err(Error::new(
1791 Code::Invalid,
1792 "all slot states must be STABLE to start a slot migration task.",
1793 ));
1794 }
1795 if let Some((from, to)) = server.cluster.asm.overlapping_import(ranges) {
1796 return Err(Error::fmt(
1797 Code::Invalid,
1798 format_args!("overlapping import exists for slot range: {from}-{to}"),
1799 ));
1800 }
1801 let mut owner = None;
1802 for &(from, to) in ranges {
1803 for slot in from..=to {
1804 let Some(at) = map.owner[usize::from(slot)] else {
1805 return Err(Error::fmt(
1806 Code::Invalid,
1807 format_args!("slot has no owner: {slot}"),
1808 ));
1809 };
1810 if *owner.get_or_insert(at) != at {
1811 return Err(Error::new(
1812 Code::Invalid,
1813 "slots belong to different source nodes",
1814 ));
1815 }
1816 }
1817 }
1818 let at = owner.unwrap_or(0);
1819 if at == 0 {
1820 return Err(Error::new(
1821 Code::Invalid,
1822 "this node is already the owner of the slot range",
1823 ));
1824 }
1825 let node = &map.nodes[usize::from(at)];
1826 Ok((node.id.as_bytes().to_vec(), node.host.clone(), node.port))
1827}
1828
1829fn slot_ranges(args: &Args<'_>, from: usize) -> Result<Vec<(u16, u16)>> {
1838 let count = args.len().saturating_sub(from);
1839 if count < 2 || !count.is_multiple_of(2) {
1840 return Err(wrong_sub_arity(args.get(1)));
1841 }
1842 if count / 2 >= SLOTS {
1843 return Err(Error::fmt(
1844 Code::Invalid,
1845 format_args!("invalid number of slot ranges: {}", count / 2),
1846 ));
1847 }
1848 let mut ranges: Vec<(u16, u16)> = Vec::with_capacity(count / 2);
1849 let mut at = from;
1850 while at < args.len() {
1851 ranges.push((slot_arg(args, at)?, slot_arg(args, at + 1)?));
1852 at += 2;
1853 }
1854 ranges.sort_unstable();
1855 let mut joined: Vec<(u16, u16)> = Vec::with_capacity(ranges.len());
1856 for range in ranges {
1857 match joined.last_mut() {
1858 Some(last) if u32::from(last.1) + 1 == u32::from(range.0) => last.1 = range.1,
1859 _ => joined.push(range),
1860 }
1861 }
1862 let mut seen = vec![false; SLOTS];
1863 for &(start, end) in &joined {
1864 if start > end {
1865 return Err(Error::fmt(
1866 Code::Invalid,
1867 format_args!("start slot number {start} is greater than end slot number {end}"),
1868 ));
1869 }
1870 for slot in start..=end {
1871 if core::mem::replace(&mut seen[usize::from(slot)], true) {
1872 return Err(Error::fmt(
1873 Code::Invalid,
1874 format_args!("Slot {slot} specified multiple times"),
1875 ));
1876 }
1877 }
1878 }
1879 Ok(joined)
1880}
1881
1882pub(super) fn trimslots(server: &Server, args: Args<'_>, out: &mut Out) -> Result<()> {
1898 if !server.cluster_enabled() {
1899 return Err(disabled());
1900 }
1901 if !args::is(args.get(1), b"ranges") {
1902 return Err(Error::new(Code::Invalid, "missing ranges argument"));
1903 }
1904 let count = args.int(2)?;
1905 if count < 1 || count > SLOTS as i64 || args.len() as i64 != 3 + count * 2 {
1906 return Err(Error::new(Code::Invalid, "invalid number of ranges"));
1907 }
1908 let ranges = slot_ranges(&args, 3)?;
1909 {
1910 let map = server.cluster.map.lock();
1911 if map.nodes[0].is_master() {
1912 for &(from, to) in &ranges {
1913 for slot in from..=to {
1914 if map.owner[usize::from(slot)] == Some(0) {
1915 return Err(Error::fmt(
1916 Code::Invalid,
1917 format_args!("the slot {slot} is served by this node"),
1918 ));
1919 }
1920 }
1921 }
1922 }
1923 }
1924 server.trim_named_slots(&ranges);
1925 out.ok();
1926 Ok(())
1927}
1928
1929fn syncslots(server: &Server, session: &mut Session, args: Args<'_>, out: &mut Out) -> Result<()> {
1945 if !session.internal() {
1946 session.hang_up();
1950 return Err(Error::new(
1951 Code::Invalid,
1952 "CLUSTER SYNCSLOTS subcommands are only allowed for internal clients",
1953 ));
1954 }
1955 let action = args.get(2);
1956 if !server.cluster.map.lock().nodes[0].is_master() {
1957 if !session.serving_master() {
1964 session.hang_up();
1965 return Err(Error::new(
1966 Code::Invalid,
1967 "CLUSTER SYNCSLOTS subcommands are only allowed for master",
1968 ));
1969 }
1970 if !args::is(action, b"conf") {
1971 return Ok(());
1972 }
1973 }
1974 if args::is(action, b"sync") && args.len() >= 6 {
1975 return sync(server, session, args, out);
1976 }
1977 if args::is(action, b"rdbchannel") && args.len() == 4 {
1978 return rdbchannel(server, session, args, out);
1979 }
1980 if (args::is(action, b"snapshot-eof") || args::is(action, b"stream-eof")) && args.len() == 3 {
1981 session.hang_up();
1985 return Ok(());
1986 }
1987 if args::is(action, b"ack") && args.len() == 5 {
1988 if let Some(offset) = yo_common::num::parse_i64(args.get(4))
1993 && offset >= 0
1994 {
1995 server.asm_ack(session.row().id, args.get(3), offset as u64);
1996 }
1997 return Ok(());
1998 }
1999 if args::is(action, b"fail") && args.len() == 4 {
2000 return Ok(());
2004 }
2005 if args::is(action, b"conf") && args.len() >= 5 {
2006 return conf(server, session, args, out);
2007 }
2008 Err(args::syntax())
2009}
2010
2011fn sync(server: &Server, session: &mut Session, args: Args<'_>, out: &mut Out) -> Result<()> {
2022 if !args.len().is_multiple_of(2) {
2023 return Err(wrong_sub_arity(args.get(1)));
2024 }
2025 let ranges = slot_ranges(&args, 4)?;
2026 {
2027 let map = server.cluster.map.lock();
2028 if (0..SLOTS).any(|at| map.migrating[at].is_some() || map.importing[at].is_some()) {
2032 return Err(Error::new(
2033 Code::Invalid,
2034 "all slot states must be STABLE to start a slot migration task.",
2035 ));
2036 }
2037 let mut source = None;
2038 for slot in ranges.iter().flat_map(|(from, to)| *from..=*to) {
2039 let Some(owner) = map.owner[usize::from(slot)] else {
2040 return Err(Error::fmt(
2041 Code::Invalid,
2042 format_args!("slot has no owner: {slot}"),
2043 ));
2044 };
2045 if *source.get_or_insert(owner) != owner {
2046 return Err(Error::new(
2047 Code::Invalid,
2048 "slots belong to different source nodes",
2049 ));
2050 }
2051 }
2052 if source != Some(0) {
2053 return Err(Error::new(
2054 Code::Invalid,
2055 "This node is not the owner of the slots",
2056 ));
2057 }
2058 let dest = session.node_id().to_vec();
2063 if !dest.is_empty()
2064 && !map
2065 .find(&dest)
2066 .is_some_and(|at| map.nodes[at as usize].is_master())
2067 {
2068 return Err(Error::fmt(
2069 Code::Invalid,
2070 format_args!(
2071 "Destination node {} is not a master",
2072 String::from_utf8_lossy(&dest)
2073 ),
2074 ));
2075 }
2076 }
2077 server.asm_begin_migrate(args.get(3), session.node_id(), ranges, session.row())?;
2078 out.simple(b"RDBCHANNELSYNCSLOTS");
2079 Ok(())
2080}
2081
2082fn rdbchannel(server: &Server, session: &mut Session, args: Args<'_>, out: &mut Out) -> Result<()> {
2091 let id = args.get(3);
2092 if id.len() != ID_LEN {
2093 return Err(Error::new(Code::Invalid, "Invalid task id"));
2094 }
2095 let ranges = server.asm_take_rdb_channel(id, session.row().id)?;
2096 out.simple(b"SLOTSSNAPSHOT");
2097 out.raw(&server.asm_snapshot(&ranges));
2098 Ok(())
2099}
2100
2101fn conf(server: &Server, session: &mut Session, args: Args<'_>, out: &mut Out) -> Result<()> {
2115 let mut at = 3;
2116 while at < args.len() {
2117 if at + 1 >= args.len() {
2118 super::write_error(out, &wrong_sub_arity(args.get(1)));
2119 return Ok(());
2120 }
2121 let name = args.get(at);
2122 let value = args.get(at + 1);
2123 if args::is(name, b"node-id") {
2124 if value.len() != ID_LEN {
2128 let len = value.len();
2129 super::write_error(
2130 out,
2131 &Error::fmt(Code::Invalid, format_args!("Invalid node id length {len}")),
2132 );
2133 return Ok(());
2134 }
2135 if server.cluster.map.lock().find(value).is_none() {
2136 super::write_error(
2137 out,
2138 &Error::fmt(
2139 Code::Invalid,
2140 format_args!(
2141 "Node {} not found in cluster",
2142 String::from_utf8_lossy(value)
2143 ),
2144 ),
2145 );
2146 return Ok(());
2147 }
2148 session.set_node_id(value);
2149 } else if args::is(name, b"slot-info") {
2150 if !slot_info(value) {
2151 super::write_error(
2152 out,
2153 &Error::fmt(
2154 Code::Invalid,
2155 format_args!("Invalid slot info: {}", String::from_utf8_lossy(value)),
2156 ),
2157 );
2158 return Ok(());
2159 }
2160 } else if args::is(name, b"asm-task") {
2161 if server.cluster.map.lock().nodes[0].is_master() {
2162 super::write_error(
2163 out,
2164 &Error::new(
2165 Code::Invalid,
2166 "CLUSTER SYNCSLOTS CONF ASM-TASK only allowed on replica",
2167 ),
2168 );
2169 return Ok(());
2170 }
2171 super::write_error(
2176 out,
2177 &Error::fmt(
2178 Code::Invalid,
2179 format_args!(
2180 "Failed to handle master task: {}",
2181 String::from_utf8_lossy(value)
2182 ),
2183 ),
2184 );
2185 } else if !args::is(name, b"capa") {
2186 super::write_error(
2187 out,
2188 &Error::fmt(
2189 Code::Invalid,
2190 format_args!("Unknown option {}", String::from_utf8_lossy(name)),
2191 ),
2192 );
2193 }
2194 at += 2;
2195 }
2196 out.ok();
2197 Ok(())
2198}
2199
2200fn slot_info(value: &[u8]) -> bool {
2208 let mut parts = value.split(|b| *b == b':');
2209 let Some(slot) = parts.next().and_then(yo_common::num::parse_i64) else {
2210 return false;
2211 };
2212 let Some(keys) = parts.next().and_then(yo_common::num::parse_i64) else {
2213 return false;
2214 };
2215 let Some(expires) = parts.next().and_then(yo_common::num::parse_i64) else {
2216 return false;
2217 };
2218 parts.next().is_none() && (0..SLOTS as i64).contains(&slot) && keys >= 0 && expires >= 0
2219}
2220
2221fn setslot(server: &Server, session_db: usize, args: Args<'_>, out: &mut Out) -> Result<()> {
2234 if !server.cluster.map.lock().nodes[0].is_master() {
2237 return Err(Error::new(
2238 Code::Invalid,
2239 "Please use SETSLOT only with masters.",
2240 ));
2241 }
2242 let slot = slot_arg(&args, 2)?;
2243 if server.cluster.asm.in_task(slot) {
2249 return Err(Error::fmt(
2250 Code::Invalid,
2251 format_args!(
2252 "Slot {slot} is currently in an active atomic slot migration. \
2253 CLUSTER SETSLOT cannot be used at this time. To perform a legacy slot migration \
2254 instead, first cancel the ongoing task with CLUSTER MIGRATION CANCEL"
2255 ),
2256 ));
2257 }
2258 let action = args.get(3);
2259 let wrong = || {
2260 Error::new(
2261 Code::Invalid,
2262 "Invalid CLUSTER SETSLOT action or number of arguments. Try CLUSTER HELP",
2263 )
2264 };
2265 let mut announce = false;
2269 let mut follow: Option<u16> = None;
2272 {
2273 let mut map = server.cluster.map.lock();
2274 let at = usize::from(slot);
2275 if args::is(action, b"migrating") && args.len() == 5 {
2276 if !map.mine(slot) {
2277 return Err(Error::fmt(
2278 Code::Invalid,
2279 format_args!("I'm not the owner of hash slot {slot}"),
2280 ));
2281 }
2282 let Some(to) = map.find(args.get(4)) else {
2283 return Err(dont_know(args.get(4)));
2284 };
2285 map.migrating[at] = Some(to);
2286 } else if args::is(action, b"importing") && args.len() == 5 {
2287 if map.mine(slot) {
2288 return Err(Error::fmt(
2289 Code::Invalid,
2290 format_args!("I'm already the owner of hash slot {slot}"),
2291 ));
2292 }
2293 let Some(from) = map.find(args.get(4)) else {
2294 return Err(dont_know(args.get(4)));
2295 };
2296 map.importing[at] = Some(from);
2297 } else if args::is(action, b"stable") && args.len() == 4 {
2298 map.migrating[at] = None;
2299 map.importing[at] = None;
2300 } else if args::is(action, b"node") && args.len() == 5 {
2301 let Some(to) = map.find(args.get(4)) else {
2302 return Err(unknown_node(args.get(4)));
2303 };
2304 if !map.nodes[usize::from(to)].is_master() {
2305 return Err(Error::new(Code::Invalid, "Target node is not a master"));
2306 }
2307 let was_mine = map.owner[at] == Some(0);
2308 let held = keys_in_slot(server, session_db, slot);
2316 if was_mine && to != 0 && held != 0 {
2317 return Err(Error::fmt(
2318 Code::Invalid,
2319 format_args!(
2320 "Can't assign hashslot {slot} to a different node while I still hold keys for this hash slot."
2321 ),
2322 ));
2323 }
2324 if held == 0 {
2325 map.migrating[at] = None;
2326 }
2327 map.owner[at] = Some(to);
2328 if was_mine && to != 0 && map.runs(0).is_empty() {
2332 follow = Some(to);
2333 }
2334 if to == 0 && map.importing[at].is_some() {
2339 bump_without_consensus(server, &mut map);
2340 map.importing[at] = None;
2341 announce = true;
2342 }
2343 } else {
2344 return Err(wrong());
2345 }
2346 }
2347 server.recount_coverage();
2348 save(server)?;
2349 if let Some(to) = follow
2352 && let Some(shared) = server.myself()
2353 {
2354 shared.cluster_replicate(to);
2355 }
2356 if announce {
2357 server.cluster_broadcast_pong();
2358 }
2359 out.ok();
2360 Ok(())
2361}
2362
2363fn keys_in_slot(server: &Server, at: usize, slot: u16) -> usize {
2366 let mut found = 0;
2367 server.dbs[at].keys(|key| {
2368 if key_slot(key) == slot {
2369 found += 1;
2370 }
2371 });
2372 found
2373}
2374
2375fn bump_without_consensus(server: &Server, map: &mut Map) -> bool {
2389 let highest = map
2390 .nodes
2391 .iter()
2392 .map(|n| n.epoch)
2393 .max()
2394 .unwrap_or(0)
2395 .max(server.cluster.epoch.load(Relaxed));
2396 let mine = map.nodes[0].epoch;
2397 if mine != 0 && mine == highest {
2398 return false;
2399 }
2400 map.nodes[0].epoch = server.cluster.epoch.fetch_add(1, Relaxed) + 1;
2401 true
2402}
2403
2404fn take_slots(server: &Server, ranges: &[(u16, u16)]) -> Result<()> {
2417 {
2418 let mut map = server.cluster.map.lock();
2419 for slot in ranges.iter().flat_map(|(from, to)| *from..=*to) {
2420 let at = usize::from(slot);
2421 map.owner[at] = Some(0);
2422 map.importing[at] = None;
2423 map.migrating[at] = None;
2424 }
2425 bump_without_consensus(server, &mut map);
2426 }
2427 server.recount_coverage();
2428 save(server)?;
2429 server.cluster_broadcast_pong();
2430 Ok(())
2431}
2432
2433fn flushslots(server: &Server, out: &mut Out) -> Result<()> {
2435 if server.dbs.iter().any(|db| !db.is_empty()) {
2436 return Err(Error::new(
2437 Code::Invalid,
2438 "DB must be empty to perform CLUSTER FLUSHSLOTS.",
2439 ));
2440 }
2441 {
2442 let mut map = server.cluster.map.lock();
2443 for slot in 0..SLOTS {
2444 if map.owner[slot] == Some(0) {
2445 map.owner[slot] = None;
2446 }
2447 map.migrating[slot] = None;
2448 map.importing[slot] = None;
2449 }
2450 }
2451 server.recount_coverage();
2452 save(server)?;
2453 out.ok();
2454 Ok(())
2455}
2456
2457fn bumpepoch(server: &Server, out: &mut Out) -> Result<()> {
2464 let (moved, epoch) = {
2465 let mut map = server.cluster.map.lock();
2466 let moved = bump_without_consensus(server, &mut map);
2467 (moved, map.nodes[0].epoch)
2468 };
2469 if moved {
2470 save(server)?;
2471 server.cluster_broadcast_pong();
2472 }
2473 let word = if moved { "BUMPED" } else { "STILL" };
2474 let text = yo_alloc::allow(|| format!("{word} {epoch}"));
2475 out.simple(text.as_bytes());
2476 Ok(())
2477}
2478
2479fn set_config_epoch(server: &Server, args: Args<'_>, out: &mut Out) -> Result<()> {
2482 let epoch = args.int(2)?;
2483 if epoch < 0 {
2484 return Err(Error::fmt(
2485 Code::Invalid,
2486 format_args!("Invalid config epoch specified: {epoch}"),
2487 ));
2488 }
2489 {
2490 let mut map = server.cluster.map.lock();
2491 if map.nodes[0].epoch != 0 {
2492 return Err(Error::new(
2493 Code::Invalid,
2494 "Node config epoch is already non-zero",
2495 ));
2496 }
2497 map.nodes[0].epoch = epoch as u64;
2498 }
2499 let epoch = epoch as u64;
2500 server.cluster.epoch.fetch_max(epoch, Relaxed);
2501 save(server)?;
2502 out.ok();
2503 Ok(())
2504}
2505
2506fn reset(server: &Server, args: Args<'_>, out: &mut Out) -> Result<()> {
2509 let hard = match args.opt(2) {
2510 None => false,
2511 Some(word) if args::is(word, b"hard") => true,
2512 Some(word) if args::is(word, b"soft") => false,
2513 Some(_) => return Err(args::syntax()),
2514 };
2515 if server.dbs.iter().any(|db| !db.is_empty()) {
2516 return Err(Error::new(
2517 Code::Invalid,
2518 "CLUSTER RESET can't be called with master nodes containing keys",
2519 ));
2520 }
2521 {
2522 let mut map = server.cluster.map.lock();
2523 for slot in 0..SLOTS {
2524 map.owner[slot] = None;
2525 map.migrating[slot] = None;
2526 map.importing[slot] = None;
2527 }
2528 map.nodes.truncate(1);
2529 map.nodes[0].epoch = 0;
2530 map.nodes[0].flags = FLAG_MYSELF | FLAG_MASTER;
2534 map.nodes[0].master = None;
2535 if hard {
2536 yo_alloc::allow(|| {
2537 map.nodes[0].id = String::from_utf8_lossy(&new_id()).into_owned();
2538 map.nodes[0].shard = String::from_utf8_lossy(&new_id()).into_owned();
2539 });
2540 }
2541 }
2542 if hard {
2543 server.cluster.epoch.store(0, Relaxed);
2544 }
2545 server.recount_coverage();
2546 save(server)?;
2547 out.ok();
2548 Ok(())
2549}
2550
2551fn count_keys(server: &Server, at: usize, args: Args<'_>, out: &mut Out) -> Result<()> {
2555 let n = args.int(2)?;
2556 let slot = u16::try_from(n)
2557 .ok()
2558 .filter(|s| usize::from(*s) < SLOTS)
2559 .ok_or_else(|| Error::new(Code::Invalid, "Invalid slot"))?;
2560 let mut found = 0i64;
2561 server.dbs[at].keys(|key| {
2562 if key_slot(key) == slot {
2563 found += 1;
2564 }
2565 });
2566 out.int(found);
2567 Ok(())
2568}
2569
2570fn get_keys(server: &Server, at: usize, args: Args<'_>, out: &mut Out) -> Result<()> {
2572 let slot = args.int(2)?;
2573 let count = args.int(3)?;
2574 let bad = || Error::new(Code::Invalid, "Invalid slot or number of keys");
2575 if !(0..SLOTS as i64).contains(&slot) || count < 0 {
2576 return Err(bad());
2577 }
2578 let slot = slot as u16;
2579 let want = count as usize;
2580 let start = out.len();
2581 let mut n = 0;
2582 server.dbs[at].keys(|key| {
2583 if n < want && key_slot(key) == slot {
2584 out.bulk(key);
2585 n += 1;
2586 }
2587 });
2588 out.close_array(start, n);
2589 Ok(())
2590}
2591
2592fn save(server: &Server) -> Result<()> {
2601 let path = {
2602 let file = server.cluster.file.lock();
2603 if file.is_empty() {
2604 return Ok(());
2605 }
2606 yo_alloc::allow(|| file.clone())
2607 };
2608 let epoch = server.cluster.epoch.load(Relaxed);
2609 let voted = server.cluster.vote.given();
2614 let text = yo_alloc::allow(|| {
2615 let mut s = lines(server, true);
2616 let _ = writeln!(s, "vars currentEpoch {epoch} lastVoteEpoch {voted}");
2617 s
2618 });
2619 yo_alloc::allow(|| {
2620 let temp = format!("{path}.tmp");
2621 let wrote =
2622 std::fs::write(&temp, text.as_bytes()).and_then(|()| std::fs::rename(&temp, &path));
2623 match wrote {
2624 Ok(()) => Ok(()),
2625 Err(e) => Err(Error::fmt(
2626 Code::Invalid,
2627 format_args!("cluster config file could not be written: {e}"),
2628 )),
2629 }
2630 })
2631}
2632
2633impl Server {
2634 fn reload_cluster(&mut self) -> Result<()> {
2642 let path = self.cluster_file();
2643 if path.is_empty() {
2644 return Ok(());
2645 }
2646 let text = match yo_alloc::allow(|| std::fs::read_to_string(&path)) {
2647 Ok(text) => text,
2648 Err(e) if e.kind() == std::io::ErrorKind::NotFound => return Ok(()),
2649 Err(e) => {
2650 return Err(Error::fmt(Code::Invalid, format_args!("{path}: {e}")));
2651 }
2652 };
2653 yo_alloc::allow(|| self.absorb_cluster(&text))
2654 }
2655
2656 fn absorb_cluster(&mut self, text: &str) -> Result<()> {
2658 let bad = |what: &str| Error::fmt(Code::Invalid, format_args!("{what} in cluster config"));
2659 let now = self.now_ms();
2660 let mut nodes: Vec<Node> = Vec::new();
2661 let mut owned: Vec<(String, u16, u16)> = Vec::new();
2665 let mut follows: Vec<(String, String)> = Vec::new();
2668 for line in text.lines() {
2669 let line = line.trim();
2670 if line.is_empty() {
2671 continue;
2672 }
2673 let mut words = line.split(' ');
2674 let first = words.next().unwrap_or_default();
2675 if first == "vars" {
2676 let rest: Vec<&str> = words.collect();
2679 for pair in rest.chunks(2) {
2680 if pair.len() == 2 && pair[0] == "currentEpoch" {
2681 let epoch = pair[1].parse::<u64>().map_err(|_| bad("bad epoch"))?;
2682 self.cluster.epoch.store(epoch, Relaxed);
2683 }
2684 if pair.len() == 2 && pair[0] == "lastVoteEpoch" {
2685 let epoch = pair[1].parse::<u64>().map_err(|_| bad("bad epoch"))?;
2686 self.cluster.vote.reload(epoch);
2687 }
2688 }
2689 continue;
2690 }
2691 if first.len() != ID_LEN {
2692 return Err(bad("bad node id"));
2693 }
2694 let address = words.next().ok_or_else(|| bad("missing address"))?;
2695 let named = words.next().ok_or_else(|| bad("missing flags"))?;
2696 let master = words.next().ok_or_else(|| bad("missing master"))?;
2697 let ping = words.next().ok_or_else(|| bad("missing ping time"))?;
2698 let pong = words.next().ok_or_else(|| bad("missing pong time"))?;
2699 let epoch = words
2700 .next()
2701 .and_then(|w| w.parse::<u64>().ok())
2702 .ok_or_else(|| bad("bad config epoch"))?;
2703 let _link = words.next();
2705 let (host, port, bus, shard) =
2706 split_address(address).ok_or_else(|| bad("bad address"))?;
2707 for word in words {
2708 if word.starts_with('[') {
2712 continue;
2713 }
2714 let (from, to) = match word.split_once('-') {
2715 Some((a, b)) => (
2716 a.parse::<u16>().map_err(|_| bad("bad slot"))?,
2717 b.parse::<u16>().map_err(|_| bad("bad slot"))?,
2718 ),
2719 None => {
2720 let one = word.parse::<u16>().map_err(|_| bad("bad slot"))?;
2721 (one, one)
2722 }
2723 };
2724 if usize::from(from) >= SLOTS || usize::from(to) >= SLOTS || from > to {
2725 return Err(bad("slot out of range"));
2726 }
2727 owned.push((first.to_owned(), from, to));
2728 }
2729 let mut flags = 0u16;
2730 for name in named.split(',') {
2731 flags |= match name {
2732 "myself" => FLAG_MYSELF,
2733 "master" => FLAG_MASTER,
2734 "slave" => FLAG_SLAVE,
2735 "fail?" => FLAG_PFAIL,
2736 "fail" => FLAG_FAIL,
2737 "handshake" => FLAG_HANDSHAKE,
2738 "noaddr" => FLAG_NOADDR,
2739 "nofailover" => FLAG_NOFAILOVER,
2740 _ => 0,
2741 };
2742 }
2743 if master != "-" {
2744 if master.len() != ID_LEN {
2745 return Err(bad("bad master id"));
2746 }
2747 follows.push((first.to_owned(), master.to_owned()));
2748 }
2749 let node = Node {
2750 id: first.to_owned(),
2751 host,
2752 port,
2753 bus,
2754 shard,
2755 epoch,
2756 flags,
2757 master: None,
2758 ping_sent: stamp(ping, now),
2763 pong_recv: stamp(pong, now),
2764 data_recv: now,
2765 fail_time: 0,
2766 offset: 0,
2767 linked: false,
2771 voted_time: 0,
2776 reports: Vec::new(),
2777 };
2778 if named.split(',').any(|f| f == "myself") {
2779 nodes.insert(0, node);
2781 } else {
2782 nodes.push(node);
2783 }
2784 }
2785 if nodes.is_empty() {
2786 return Ok(());
2787 }
2788 let mut map = Map::new(nodes.remove(0));
2789 map.nodes.append(&mut nodes);
2790 for (id, from, to) in owned {
2791 let Some(at) = map.find(id.as_bytes()) else {
2792 return Err(bad("slots for a node nobody knows"));
2793 };
2794 for slot in from..=to {
2795 map.owner[usize::from(slot)] = Some(at);
2796 }
2797 }
2798 for (who, whose) in follows {
2799 let (Some(who), Some(whose)) = (map.find(who.as_bytes()), map.find(whose.as_bytes()))
2800 else {
2801 return Err(bad("master id nobody knows"));
2802 };
2803 map.nodes[usize::from(who)].master = Some(whose);
2804 map.nodes[usize::from(who)].epoch = 0;
2809 }
2810 *self.cluster.map.lock() = map;
2811 Ok(())
2812 }
2813}
2814
2815fn stamp(field: &str, now: u64) -> u64 {
2822 match field.parse::<u64>() {
2823 Ok(0) | Err(_) => 0,
2824 Ok(_) => now,
2825 }
2826}
2827
2828fn split_address(field: &str) -> Option<(String, u16, u16, String)> {
2835 let (address, aux) = match field.split_once(',') {
2836 Some((address, aux)) => (address, aux),
2837 None => (field, ""),
2838 };
2839 let (host, ports) = address.rsplit_once(':')?;
2840 let (client, bus) = match ports.split_once('@') {
2841 Some((client, bus)) => (client, bus.parse::<u16>().ok()?),
2842 None => (ports, 0),
2843 };
2844 let port = client.parse::<u16>().ok()?;
2845 let bus = if bus == 0 { port + BUS_OFFSET } else { bus };
2846 let shard = aux
2847 .split(',')
2848 .find_map(|pair| pair.strip_prefix("shard-id="))
2849 .map_or_else(
2850 || String::from_utf8_lossy(&new_id()).into_owned(),
2851 str::to_owned,
2852 );
2853 Some((host.to_owned(), port, bus, shard))
2854}
2855
2856#[cfg(test)]
2866impl Server {
2867 pub(super) fn cluster_own_everything(&self) {
2869 {
2870 let mut map = self.cluster.map.lock();
2871 for slot in 0..SLOTS {
2872 map.owner[slot] = Some(0);
2873 }
2874 }
2875 self.recount_coverage();
2876 self.cluster.was_down.store(false, Relaxed);
2877 self.cluster.booted_at.store(0, Relaxed);
2878 }
2879
2880 pub(super) fn cluster_pretend_node(&self, id: &str, host: &str, port: u16) -> u16 {
2882 let mut map = self.cluster.map.lock();
2883 let mut node = Node::new(
2884 id.to_owned(),
2885 host.to_owned(),
2886 port,
2887 port + BUS_OFFSET,
2888 FLAG_MASTER,
2889 0,
2890 );
2891 node.shard = id.to_owned();
2892 node.linked = true;
2893 map.nodes.push(node);
2894 (map.nodes.len() - 1) as u16
2895 }
2896
2897 pub(super) fn cluster_hand_over(&self, slot: u16, node: u16) {
2899 let mut map = self.cluster.map.lock();
2900 map.owner[usize::from(slot)] = Some(node);
2901 }
2902
2903 pub(super) fn cluster_pretend_follower(&self, of: u16) {
2909 let mut map = self.cluster.map.lock();
2910 map.nodes[0].flags &= !FLAG_MASTER;
2911 map.nodes[0].flags |= FLAG_SLAVE;
2912 map.nodes[0].master = Some(of);
2913 }
2914
2915 pub(super) fn cluster_moving(&self, slot: u16, to: Option<u16>, from: Option<u16>) {
2917 let mut map = self.cluster.map.lock();
2918 map.migrating[usize::from(slot)] = to;
2919 map.importing[usize::from(slot)] = from;
2920 }
2921}
2922
2923#[cfg(test)]
2926mod tests {
2927 use super::{SLOTS, key_slot};
2928
2929 #[test]
2936 fn setslot_will_not_touch_a_slot_a_migration_is_moving() {
2937 use crate::proto::{Limits, Proto};
2938 use crate::reply::Out;
2939 use crate::request::{Argv, Step};
2940
2941 let mut server = super::Server::new();
2942 server.enable_cluster("", 7000);
2943 server.cluster_own_everything();
2944 let other = server.cluster_pretend_node(
2945 "5b1e2ce29b1e0c86bd53ee1e5b0dd7b66c0e6e0f",
2946 "10.0.0.9",
2947 7002,
2948 );
2949 assert_eq!(other, 1);
2950
2951 let said = |server: &super::Server, argv: &[&[u8]]| {
2952 let mut wire = Vec::new();
2953 wire.extend_from_slice(format!("*{}\r\n", argv.len()).as_bytes());
2954 for arg in argv {
2955 wire.extend_from_slice(format!("${}\r\n", arg.len()).as_bytes());
2956 wire.extend_from_slice(arg);
2957 wire.extend_from_slice(b"\r\n");
2958 }
2959 let mut decoded = Argv::new();
2960 assert!(matches!(
2961 decoded.decode(&wire, &Limits::default()).unwrap(),
2962 Step::Command { .. }
2963 ));
2964 let args = super::Args::new(&decoded, &wire);
2965 let mut out = Out::new(Proto::Resp2);
2966 match super::setslot(server, 0, args, &mut out) {
2967 Ok(()) => String::from_utf8_lossy(out.as_slice()).into_owned(),
2968 Err(e) => e.to_string(),
2969 }
2970 };
2971
2972 assert_eq!(
2974 said(&server, &[b"CLUSTER", b"SETSLOT", b"100", b"STABLE"]),
2975 "+OK\r\n"
2976 );
2977
2978 server
2979 .asm_begin_import(vec![b'b'; 40], vec![(100, 200)])
2980 .expect("nothing else is running");
2981 for slot in [b"100".as_slice(), b"150", b"200"] {
2982 let got = said(&server, &[b"CLUSTER", b"SETSLOT", slot, b"STABLE"]);
2983 let want = format!(
2984 "Slot {} is currently in an active atomic slot migration. \
2985 CLUSTER SETSLOT cannot be used at this time. To perform a legacy slot migration \
2986 instead, first cancel the ongoing task with CLUSTER MIGRATION CANCEL",
2987 String::from_utf8_lossy(slot)
2988 );
2989 assert!(got.ends_with(&want), "{got:?}");
2990 }
2991 assert_eq!(
2994 said(&server, &[b"CLUSTER", b"SETSLOT", b"99", b"STABLE"]),
2995 "+OK\r\n"
2996 );
2997 assert_eq!(
2998 said(&server, &[b"CLUSTER", b"SETSLOT", b"201", b"STABLE"]),
2999 "+OK\r\n"
3000 );
3001 server.cluster.asm.cancel(None, 1);
3003 assert_eq!(
3004 said(&server, &[b"CLUSTER", b"SETSLOT", b"150", b"STABLE"]),
3005 "+OK\r\n"
3006 );
3007 }
3008
3009 #[test]
3012 fn a_key_lands_in_the_slot_a_real_server_puts_it_in() {
3013 assert_eq!(key_slot(b"foo"), 12182);
3014 assert_eq!(key_slot(b"1234"), 6025);
3015 assert_eq!(key_slot(b""), 0);
3016 assert_eq!(key_slot(b"{user1000}.following"), 3443);
3017 }
3018
3019 #[test]
3022 fn the_hash_tag_rules_are_the_reference_rules() {
3023 assert_eq!(
3025 key_slot(b"{user1000}.following"),
3026 key_slot(b"{user1000}.followers")
3027 );
3028 assert_eq!(key_slot(b"{}foo"), key_slot(b"{}foo"));
3030 assert_ne!(key_slot(b"{}foo"), key_slot(b"foo"));
3031 assert_ne!(key_slot(b"{foo"), key_slot(b"foo"));
3033 assert_eq!(key_slot(b"{a}{b}"), key_slot(b"a"));
3035 assert_eq!(key_slot(b"foo{{bar}}zap"), key_slot(b"{bar"));
3037 }
3038
3039 #[test]
3042 fn every_slot_is_in_range() {
3043 let mut seen = vec![false; SLOTS];
3044 for i in 0..200_000u32 {
3045 let key = i.to_string();
3046 let slot = key_slot(key.as_bytes());
3047 assert!(usize::from(slot) < SLOTS);
3048 seen[usize::from(slot)] = true;
3049 }
3050 assert!(seen.iter().all(|s| *s), "200k keys reach all 16384 slots");
3051 }
3052
3053 #[test]
3062 fn a_config_file_puts_every_run_on_the_node_that_owns_it() {
3063 let mut server = super::Server::new();
3064 server.enable_cluster("", 7355);
3065 let text = "\
30663b80b05445f38bc7214f083696a2bbf90e3f30e3 127.0.0.1:7356@17356 master - 0 0 0 connected 10923-16383
306730d0651b0ec5e178e082634c44fb9adcc6e4021b 127.0.0.1:7355@17355 myself,master - 0 0 1 connected 5461-10922
306819a9e69b8b66016ac43c55ccdeed0283e0148e17 127.0.0.1:7354@17354 master - 0 0 2 connected 0-5460
3069vars currentEpoch 2 lastVoteEpoch 0
3070";
3071 server.absorb_cluster(text).expect("the file parses");
3072 let map = server.cluster.map.lock();
3073 let at = |id: &str| map.find(id.as_bytes()).expect("the node is in the table");
3074 assert_eq!(at("30d0651b0ec5e178e082634c44fb9adcc6e4021b"), 0, "myself");
3075 for (id, from, to) in [
3076 ("19a9e69b8b66016ac43c55ccdeed0283e0148e17", 0, 5460),
3077 ("30d0651b0ec5e178e082634c44fb9adcc6e4021b", 5461, 10922),
3078 ("3b80b05445f38bc7214f083696a2bbf90e3f30e3", 10923, 16383),
3079 ] {
3080 let owner = Some(at(id));
3081 for slot in from..=to {
3082 assert_eq!(map.owner[slot], owner, "slot {slot} belongs to {id}");
3083 }
3084 }
3085 }
3086
3087 #[test]
3091 fn a_config_file_is_not_read_back_as_live_state() {
3092 let mut server = super::Server::new();
3093 server.enable_cluster("", 7357);
3094 let text = "\
30959fcbb7624dedbb2fd0020dd2fbf86a5eb8cec31b 127.0.0.1:7355@17355 master - 0 1789005531306 3 connected 5461-10922
3096ac6dd51a69741dc5130637c594866c9b7e0cfc4e 127.0.0.1:7357@17357 myself,slave 9fcbb7624dedbb2fd0020dd2fbf86a5eb8cec31b 1789005400000 1789005532315 3 connected
3097";
3098 server.absorb_cluster(text).expect("the file parses");
3099 let now = server.now_ms();
3100 let map = server.cluster.map.lock();
3101 for node in &map.nodes {
3102 assert!(!node.linked, "nothing is linked before the bus dials out");
3103 }
3104 assert_eq!(map.nodes[0].ping_sent, now, "myself had a ping in flight");
3107 assert_eq!(map.nodes[1].ping_sent, 0, "the master did not");
3108 assert_eq!(map.nodes[0].pong_recv, now);
3109 assert_eq!(map.nodes[0].epoch, 0, "the replica's epoch is dropped");
3111 assert_eq!(map.nodes[1].epoch, 3, "the master's is kept");
3112 }
3113}