Skip to main content

yo_resp/dispatch/
cluster.rs

1//! Cluster mode: the 16384 slots, who owns them, and what a client is told
2//! when it asks the wrong node.
3//!
4//! A Redis cluster has no coordinator and no lookup service. Every key belongs
5//! to one of 16384 slots, worked out from the key name alone, and every node
6//! knows which node owns which slot. A client that guesses wrong is not proxied,
7//! it is told where to go, and it remembers. So the whole of routing is a
8//! function from a key to a number and a table from that number to a node, and
9//! both of them are here.
10//!
11//! # The slot
12//!
13//! `CRC16(key) mod 16384`, with one rule on top: if the key contains a `{`
14//! followed later by a `}` with at least one byte between them, only the bytes
15//! between them are hashed. That is the hash tag, and it is the only way a
16//! client has of making two keys land together, which is the only way a command
17//! that names two keys can be run at all. `{user1000}.following` and
18//! `{user1000}.followers` are one slot and one node.
19//!
20//! # The table
21//!
22//! 16384 entries, each naming the node that owns that slot or nobody. A slot
23//! nobody owns is a hole, and a cluster with a hole in it is down: rather than
24//! guess, every node refuses every key in the hole and, with
25//! `cluster-require-full-coverage` left on, refuses every key at all. That is
26//! severe on purpose. A cluster that answers for the slots it happens to have is
27//! a cluster that silently loses the rest.
28//!
29//! Two more tables sit beside it for the slots that are moving. A slot this node
30//! owns and is sending away is migrating, and a key in it that is already gone
31//! is answered with `ASK`, which is a redirection for one command rather than
32//! for the slot. A slot this node does not own and is receiving is importing, and
33//! a command lands on it only if the connection said `ASKING` first. The pair is
34//! what makes a slot move without a window where a key is on neither node.
35//!
36//! # What is here and what is not
37//!
38//! The slots, the table, the whole `CLUSTER` container, the redirections and the
39//! configuration file that carries all of it across a restart. The bus, which is
40//! the binary protocol nodes talk to each other over and the thing that actually
41//! fills the table, is next door in [`bus`]. Nothing in this file dials anybody:
42//! it reads the table and edits it, and a change an operator makes here is
43//! announced by asking the bus to send a packet once the lock is gone.
44//!
45//! Failover is not here either, in both of its forms. A replica whose master
46//! dies promotes itself and an operator can stand a master down by hand with
47//! `CLUSTER FAILOVER`, and the election, the handshake and the pause behind both
48//! of those are in [`bus`] with everything else that talks to another node.
49
50use 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
69/// How many slots a cluster has, which is Redis's number and is not a setting.
70///
71/// It is 16384 rather than a round number because the bus gossips a bitmap of
72/// every slot in every heartbeat, and 16384 bits is two kilobytes, which is
73/// what the authors were willing to spend on a packet sent every second.
74pub const SLOTS: usize = 16384;
75
76/// How long a cluster has to be fully covered before it says it is up.
77///
78/// Redis waits before turning `cluster_state` from fail to ok, so a node that
79/// has just been given its slots does not announce itself ready in the same
80/// instant and then take it back when the next heartbeat disagrees. The number
81/// is Redis's `CLUSTER_WRITABLE_DELAY`.
82const WRITABLE_DELAY_MS: u64 = 2000;
83
84/// How long a node that has been down waits before saying it is up again.
85///
86/// The two delays are different because they are about different things. The
87/// first is about a node that has only just started and has never been anything
88/// but ready. The second is about a node that was in the minority and has just
89/// seen the coverage come back, which is the case where being hasty is how a
90/// healed partition serves a key that has already been elected away somewhere
91/// else. Redis works it out as the node timeout clamped between 500ms and 5s,
92/// and with the default fifteen second timeout that is five seconds every time.
93const REJOIN_DELAY_MS: u64 = 5000;
94
95/// What the bus port is, which is the client port plus this.
96const BUS_OFFSET: u16 = 10000;
97
98/// How long a node id is, in hex characters.
99const ID_LEN: usize = 40;
100
101// ----------------------------------------------------------------- the slot
102
103/// The CRC16 table Redis hashes keys with, which is CCITT with the XMODEM
104/// parameters: polynomial 0x1021, no reflection, no initial value and no final
105/// exclusive or.
106///
107/// Written out rather than computed at startup because it is what every key on
108/// the command path goes through, and a table in the binary is a table that is
109/// already in cache by the time the first command arrives.
110#[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/// The CRC16 of `data`, which is only ever used to pick a slot.
147#[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/// Which slot `key` belongs to.
158///
159/// The hash tag rule is Redis's `keyHashSlot` and its edge cases are worth being
160/// exact about, because a client library implements the same rule and the two
161/// have to agree on every key or the client routes to a node that will not serve
162/// it. A `{` with no `}` after it hashes the whole key. A `{}` with nothing
163/// between hashes the whole key. Only the first `}` after the first `{` counts,
164/// so `{a}{b}` hashes `a`. And a key with no braces at all hashes whole, which
165/// is nearly every key there has ever been.
166#[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            // The empty tag is not a tag, which is the `if (e == s+1)` arm.
171            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
179// ---------------------------------------------------------------- the table
180
181/// The reference's own node flag bits.
182///
183/// Kept as its bits rather than as a set of booleans because they go out on the
184/// bus in exactly this shape and come back in it, so anything else would be two
185/// translations for no gain.
186pub(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/// One node in the cluster, which is this one and everybody the bus has found.
199#[derive(Clone)]
200struct Node {
201    /// The forty hex characters that name it, which never change while it lives.
202    id: String,
203    /// Where a client reaches it. Empty for this node, which is what a real
204    /// server reports for itself until something tells it its own address.
205    host: String,
206    /// The port a client reaches it on.
207    port: u16,
208    /// The port the bus reaches it on, which is nearly always the client port
209    /// plus ten thousand but is carried separately because a node behind a port
210    /// map announces something else.
211    bus: u16,
212    /// Which shard it belongs to, which is a master and its replicas.
213    shard: String,
214    /// The config epoch it claims its slots under.
215    epoch: u64,
216    /// What it is and how it is doing, in the reference's bits.
217    flags: u16,
218    /// The master it follows, as an index into the same table.
219    master: Option<u16>,
220    /// When a ping went out to it with no pong back yet, or nought for none
221    /// outstanding, which is the field the timeout is measured from.
222    ping_sent: u64,
223    /// When the last pong came back.
224    pong_recv: u64,
225    /// When anything at all last arrived from it. A node under a heavy pub/sub
226    /// load can be slow with its pong and perfectly alive, so traffic of any
227    /// kind counts as proof it is there.
228    data_recv: u64,
229    /// When this node decided it had failed.
230    fail_time: u64,
231    /// The replication offset it last put in a packet.
232    offset: u64,
233    /// Whether there is an outbound link to it at the moment.
234    linked: bool,
235    /// When this node last voted in an election about replacing it, which only
236    /// means anything on a master and is what stops two of its replicas being
237    /// promoted one after the other.
238    voted_time: u64,
239    /// Who has said it is unreachable and when they last said so, which is the
240    /// weak quorum the FAIL flag needs.
241    reports: Vec<(String, u64)>,
242}
243
244impl Node {
245    /// A node with nothing known about it but where it is.
246    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            // Both stamps start at zero and not at now, which is the reference's
258            // own rule and is what makes the `myself` line read `0 0`. Nothing
259            // has been heard from a node that was made a moment ago, and saying
260            // otherwise would also hold off the first ping for a second.
261            pong_recv: 0,
262            // This one does start at now, because it stands in for the creation
263            // time as well and the handshake timeout is measured against it.
264            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    /// Whether it is a master, which is the reference's rule that anything not
274    /// flagged a replica counts as one.
275    fn is_master(&self) -> bool {
276        self.flags & FLAG_SLAVE == 0
277    }
278
279    /// Whether it is down as far as anybody can tell.
280    fn down(&self) -> bool {
281        self.flags & (FLAG_PFAIL | FLAG_FAIL) != 0
282    }
283
284    /// The comma separated flag list a `CLUSTER NODES` line carries, in the
285    /// reference's own order, with `noflags` for a node with none of them.
286    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    /// The `ip:port@bus` field of a `CLUSTER NODES` line.
314    fn address(&self, into: &mut String) {
315        let _ = write!(into, "{}:{}@{}", self.host, self.port, self.bus);
316    }
317
318    /// The same field with the aux fields on the end of it, which is what goes in
319    /// the config file and not what goes on the wire. The reference keeps them
320    /// off `CLUSTER NODES` on purpose, because a client that learned to parse
321    /// them would break the next time somebody added one.
322    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
328/// Who owns what, and what is moving.
329///
330/// One lock over all three tables rather than one each, because every reader
331/// wants a consistent answer across them: a slot is owned, or migrating, or
332/// importing, and a reader that saw two of those from different instants would
333/// route a client to a node that had already handed the slot on.
334struct Map {
335    /// Every node this one knows about, with this one first.
336    nodes: Vec<Node>,
337    /// Which node owns each slot, as an index into `nodes`, or `None`.
338    owner: Vec<Option<u16>>,
339    /// The slots this node owns and is sending away, and to whom.
340    migrating: Vec<Option<u16>>,
341    /// The slots this node is receiving, and from whom.
342    importing: Vec<Option<u16>>,
343}
344
345impl Map {
346    /// An empty table with room for every slot.
347    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    /// The index of the node with this id, or `None` for one nobody knows.
357    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    /// Drop a node and everything that pointed at it.
365    ///
366    /// Every other table here holds an index into `nodes`, so dropping one out
367    /// of the middle means walking them and shifting anything above it down.
368    /// That is fine because a node leaves a cluster about as often as an
369    /// operator types, and holding ids instead of indices everywhere would cost
370    /// a string compare on the routing path, which is the one path that has to
371    /// be fast.
372    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    /// How many masters are serving at least one slot and have not failed, which
390    /// is the electorate a FAIL vote is counted against.
391    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    /// Whether this node owns `slot`, which is index nought owning it.
400    fn mine(&self, slot: u16) -> bool {
401        self.owner[slot as usize] == Some(0)
402    }
403
404    /// How many slots have an owner.
405    fn assigned(&self) -> usize {
406        self.owner.iter().filter(|o| o.is_some()).count()
407    }
408
409    /// How many nodes are serving at least one slot, which is Redis's
410    /// `cluster_size` and is not the same as how many nodes there are.
411    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    /// The runs of consecutive slots one node owns, in order.
420    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
435// -------------------------------------------------------------- the settings
436
437/// Everything about cluster mode, all of it idle on a server that was not
438/// started with it on, which is nearly every server.
439pub(crate) struct Cluster {
440    /// Whether `--cluster-enabled yes` was given. Immutable for the life of the
441    /// process, which is Redis's rule and the only sane one: a server that could
442    /// be turned into a cluster node while it was holding keys would be a server
443    /// whose keys were suddenly in slots it did not own.
444    on: bool,
445    /// The tables, empty on a server that is not a cluster node.
446    map: Lock<Map>,
447    /// Redis's `currentEpoch`, which is the highest epoch anybody has claimed.
448    epoch: AtomicU64,
449    /// When the coverage last became complete, for the delay before this node
450    /// says the cluster is up. Nought means it is not covered.
451    covered_at: AtomicU64,
452    /// When cluster mode was turned on, which is the other end of the shorter
453    /// delay.
454    booted_at: AtomicU64,
455    /// Whether this node has been uncovered at any point since it started, which
456    /// is what picks between the two delays.
457    was_down: AtomicBool,
458    /// Whether every slot has to have an owner before anything is served, which
459    /// is Redis's `cluster-require-full-coverage` and is on by default.
460    full_coverage: AtomicBool,
461    /// Whether a read is still served while the cluster is down, which is
462    /// Redis's `cluster-allow-reads-when-down` and is off by default.
463    ///
464    /// Off is the safe answer and it is the one a real server ships with. A node
465    /// that cannot see the whole cluster does not know whether the part it
466    /// cannot see has already elected somebody else, so a read it served could
467    /// be a read of a key that has moved on. An operator who would rather have
468    /// stale reads than no reads turns it on and knows what they bought.
469    reads_when_down: AtomicBool,
470    /// Where the table is written, under the server's `dir`.
471    file: Lock<String>,
472    /// The links, the blacklist and the secret, none of which exist until the
473    /// bus is started and none of which a command reads on the hot path.
474    bus: bus::Bus,
475    /// The election this node is standing in or voting in, if any.
476    vote: bus::Vote,
477    /// The manual failover this node is in, on whichever side of it.
478    manual: bus::Manual,
479    /// The slot migration this node is in, and the ones it has been in.
480    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    /// Whether this server is a cluster node, which is one relaxed read of a
510    /// field that is false on nearly every server there is.
511    #[must_use]
512    pub fn cluster_enabled(&self) -> bool {
513        self.cluster.on
514    }
515
516    /// Turn cluster mode on, which only whoever built the server may do.
517    ///
518    /// Called before the first connection is accepted and never again, which is
519    /// why it takes `&mut self`: there is no lock here because there is nobody
520    /// to race with.
521    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        // Under the server's directory when the name is a bare one, which is
536        // what a real server does with `cluster-config-file`: it takes the
537        // directory first and then opens everything relative to it. No name at
538        // all is a node that keeps nothing across a restart, which is not
539        // something a real server offers and is what a test wants.
540        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            // The secret nodes recognise each other by is made here rather than
551            // when the bus starts, because a node with no bus still has to have
552            // one: the whole cluster ends up on the smallest secret anybody
553            // started with, and a node holding an empty string would win that
554            // and leave everybody with no secret at all.
555            *self.cluster.bus.secret.lock() = String::from_utf8_lossy(&new_id()).into_owned();
556        });
557        // Whatever was written last time this node ran, if anything was. A node
558        // that comes back without its slots is a node that has silently given
559        // its half of the keyspace away, so a file that is there and unreadable
560        // is worth more noise than a file that is not there at all.
561        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    /// The secret one node authenticates to another node's client port with.
568    ///
569    /// Forty hex characters on a cluster node and empty on anything else, which
570    /// is what makes `AUTH "internal connection"` impossible to get past on a
571    /// server that is not in a cluster: there is no secret to guess.
572    ///
573    /// Handed out as a copy because it is read on `AUTH` and nowhere else, so
574    /// one allocation on a command that runs once a connection is cheaper than
575    /// keeping the lock held across a comparison.
576    pub(crate) fn cluster_secret(&self) -> String {
577        let held = self.cluster.bus.secret.lock();
578        yo_alloc::allow(|| held.clone())
579    }
580
581    /// Whether every slot has to be covered before anything is served.
582    pub(crate) fn cluster_full_coverage(&self) -> bool {
583        self.cluster.full_coverage.load(Relaxed)
584    }
585
586    /// Whether a read is served while the cluster is down.
587    pub(crate) fn cluster_reads_when_down(&self) -> bool {
588        self.cluster.reads_when_down.load(Relaxed)
589    }
590
591    /// Set either of the two, which `CONFIG SET` does and which really move: a
592    /// cluster with a hole in it starts answering for the slots it does have.
593    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    /// Where the table is written, which `CONFIG GET` reports.
600    pub(crate) fn cluster_file(&self) -> String {
601        let file = self.cluster.file.lock();
602        yo_alloc::allow(|| file.clone())
603    }
604
605    /// This node's id, which is what `CLUSTER MYID` answers.
606    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    /// Whether the cluster is up, which is every slot covered for long enough.
612    ///
613    /// A hole makes it down, and so does having just been covered, for the delay
614    /// a real server waits. `cluster-require-full-coverage no` turns the first
615    /// half off, and then a node with any slots at all says it is up and refuses
616    /// only the keys in the hole.
617    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    /// Look at the coverage again and start or stop the clock on it.
631    ///
632    /// Called after anything that moves a slot. Cheap enough to do on the spot
633    /// rather than in a cron, and doing it on the spot is what makes
634    /// `CLUSTER ADDSLOTS` followed by `CLUSTER INFO` agree.
635    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
657/// Forty hex characters from the same entropy the replication id comes from.
658fn 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
670// ------------------------------------------------------------ the redirects
671
672/// Whether the command carries its own `ASKING` rather than needing one sent.
673///
674/// One command does, `RESTORE-ASKING`, and the reference spells that as a flag on
675/// the command rather than as a name the routing gate knows, so this reads the
676/// flag. It is what makes a slot migration work at all: the node being sent the
677/// keys does not own the slot yet, and a client that had to send `ASKING` in
678/// front of every key would double the round trips for no reason.
679pub(super) fn asks(spec: &Spec) -> bool {
680    spec.flags.contains(&"asking")
681}
682
683/// Where a command's keys say it should run, or `None` for run it here.
684///
685/// Called for every command on a cluster node and for none at all on a server
686/// that is not one, which is the reason the enabled flag is read first and is a
687/// plain field rather than anything atomic. A command that names no key never
688/// redirects, whatever the state of the cluster, which is what lets a client
689/// send `PING` and `SUBSCRIBE` to a node that is not serving anything.
690///
691/// The order the checks go in is `getNodeByQuery`'s, and it is not the order
692/// anybody would guess, so it is worth writing down. The first key decides the
693/// slot and the node, and a first key in a slot nobody owns is
694/// `CLUSTERDOWN Hash slot not served` before the rest of the keys have even been
695/// looked at, so a command whose keys are in two slots and whose first slot is a
696/// hole is told about the hole and not about the two slots. Then the rest of the
697/// keys, and one in another slot is `CROSSSLOT`. Then the health of the cluster
698/// as a whole. Then the slot being handed over, which is where `ASK` and
699/// `TRYAGAIN` come from. And `MOVED` last, because it is the answer left when
700/// nothing else applies and the node is not this one.
701pub(super) fn gate(
702    server: &Server,
703    db: usize,
704    asking: bool,
705    spec: &Spec,
706    args: Args<'_>,
707) -> Option<Error> {
708    // Nothing to route. Cheap and first, because a container command and every
709    // connection command land here too.
710    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        // The slot of the first key, and then whether everything else agrees.
721        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    // The first key's slot having no owner beats everything, including a command
746    // whose keys are in two slots.
747    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    // A cluster with a hole in it somewhere else. Reads are still served when
760    // the operator asked for that, writes never are, because a write to a
761    // cluster that cannot see all of itself is a write that may be about to be
762    // written somewhere else as well.
763    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    // The slot is moving, so which keys are still here decides the answer. This
775    // is the only part of the gate that touches the keyspace, and it only runs
776    // for a slot that somebody is in the middle of handing over.
777    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        // Some of them are here and some have gone, so there is no node that can
799        // answer the whole command and the client is told to come back.
800        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
827/// Where a node is reachable, copied out while the lock is held.
828fn 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
833/// A `MOVED` or an `ASK` line, which are the same shape with a different word.
834///
835/// A node with no address of its own is written as the loopback, because a
836/// redirection has to name somewhere a client can connect to and an empty host
837/// is not one. A real server does the same when it has nothing better.
838fn 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
850// ------------------------------------------------------------- the container
851
852/// `CLUSTER <subcommand> ...`.
853pub(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    // Every subcommand but `HELP` and the two that are only about arguments is
862    // refused outright on a server that was not started as a cluster node, which
863    // is Redis's rule and covers the whole container including `HELP` itself.
864    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            // TAKEOVER implies FORCE, which is the reference's own line: taking
947            // over without an election is a superset of taking over without
948            // asking the master.
949            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
977/// What every subcommand answers on a server that is not a cluster node.
978pub(super) fn disabled() -> Error {
979    Error::new(Code::Invalid, "This instance has cluster support disabled")
980}
981
982/// The reference's sentence for a node id nobody has heard of.
983fn unknown_node(id: &[u8]) -> Error {
984    Error::fmt(
985        Code::Invalid,
986        format_args!("Unknown node {}", String::from_utf8_lossy(id)),
987    )
988}
989
990/// The same, in the spelling `CLUSTER SETSLOT` uses, which is a different
991/// sentence for the same thing and is the reference's.
992fn 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
999/// The arity of each subcommand, taken from the reference's own table, or `None`
1000/// for a word that is not a subcommand.
1001///
1002/// A hand written list for the same reason `CONTAINERS` is one: the command
1003/// table has a row for the container and none for what is behind it. It goes
1004/// away with D-114.
1005fn 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
1045/// Redis's arity rule: a positive number is exact and a negative one is a floor.
1046fn 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
1055/// What a subcommand says when its own parsing gave up.
1056///
1057/// The container's arity is a floor, so a subcommand that takes a fixed number
1058/// of words past that floor has to check the count itself, and when it fails the
1059/// reference does not say the arity was wrong, it says this. The subcommand is
1060/// echoed back as the caller typed it and the container is upper cased, which is
1061/// `addReplySubcommandSyntaxError` word for word.
1062fn 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
1072/// The wrong arity complaint, which names the subcommand and not the container.
1073fn 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
1083// -------------------------------------------------------------- the reports
1084
1085/// `CLUSTER INFO`, which is the same field names in the same order a real server
1086/// prints, as one bulk string rather than as a map.
1087fn 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
1122/// `CLUSTER NODES`, which is the same text this node writes to its config file.
1123fn 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
1128/// One line per node, in the reference's field order.
1129///
1130/// `<id> <ip:port@bus,aux> <flags> <master> <ping-sent> <pong-recv> <epoch>
1131/// <link-state> <slot> ...`, with the migrating and importing slots on the end
1132/// of the owner's line in square brackets.
1133fn 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
1143/// One node's line, without the newline, which `CLUSTER REPLICAS` wants one at a
1144/// time and `CLUSTER NODES` wants all of.
1145fn 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    // A replica reports its master's epoch rather than its own, which is what a
1162    // client reading the line is actually asking about.
1163    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
1192/// `CLUSTER SLOTS`, which is one entry per run of slots one node owns.
1193///
1194/// The runs come out in slot order and not in node order, because that is the
1195/// order the reference walks and a client that caches this reply by position
1196/// will notice the difference.
1197fn 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
1222/// One `CLUSTER SLOTS` entry: the run, its owner, then the owner's replicas.
1223///
1224/// A replica everybody agrees is gone is left out, and so is one whose
1225/// replication offset is still zero, which is the reference's own filter and is
1226/// its way of saying this replica has never caught up with anything and is not
1227/// somewhere to send a reader yet. This node's own offset does not come off the
1228/// table, since nothing gossips a node its own offset, so it is passed in.
1229fn 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
1253/// `CLUSTER SHARDS`, which is the same information grouped by shard rather than
1254/// by run, and is what a client library reads to find the replicas of a master.
1255fn 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        // The slots of a shard are the slots of whichever member is holding
1270        // them, which is the master of it except for the moment after a
1271        // failover when two members still think they are.
1272        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
1320/// The help text, word for word from the reference.
1321fn 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
1393// ------------------------------------------------------------ the slot moves
1394
1395/// One slot number from an argument, refused the way the reference refuses one.
1396///
1397/// One sentence covers both ways of getting it wrong, which is the reference's
1398/// `getSlotOrReply` and is not the same as what `COUNTKEYSINSLOT` says: a word
1399/// that is not a number and a number that is not a slot both come back as
1400/// `Invalid or out of range slot` here.
1401fn 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
1409/// `CLUSTER ADDSLOTS`, `DELSLOTS`, `ADDSLOTSRANGE` and `DELSLOTSRANGE`.
1410///
1411/// All four in one body because all four are the same walk with a different
1412/// stride and a different word in the complaint. Every slot is checked before
1413/// any of them is moved, which is the reference's behaviour and is what makes a
1414/// list with one bad number in it change nothing at all.
1415fn 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
1477/// The address `CLUSTER MEET` was given, checked the way the reference checks it.
1478///
1479/// The port is reported back as the caller typed it rather than as it parsed,
1480/// which is the reference's wording and matters for a port that parsed fine and
1481/// was out of range.
1482fn 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
1518/// `CLUSTER REPLICAS`, which is one `CLUSTER NODES` line per replica of a node.
1519fn 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
1548/// `CLUSTER FORGET`, which drops a node and makes the drop stick.
1549///
1550/// An id nobody has heard of is an error unless it is one this node forgot on
1551/// purpose a moment ago, in which case the answer is OK, because a tool that
1552/// sends the same forget to every node in the cluster should not hear an error
1553/// from the ones that had already been told by gossip.
1554fn 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
1581/// `CLUSTER REPLICATE`, which makes this node a replica of another.
1582///
1583/// A master with slots or keys refuses, which is the reference's rule and is the
1584/// right one: the slots would be given away silently and the keys in them would
1585/// be answered for by two nodes at once.
1586fn 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
1621/// `CLUSTER SLOT-STATS`, which is how many keys each slot this node owns holds.
1622///
1623/// A real server keeps more metrics than this one and only when
1624/// `cluster-slot-stats-enabled` is on, so the default answer is the key count on
1625/// its own and that is what is here. The count is one walk of the keyspace with a
1626/// tally per slot rather than one walk per slot, which is the same D-150 cost as
1627/// `COUNTKEYSINSLOT` paid once instead of sixteen thousand times.
1628fn 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        // The tie is broken by slot number ascending either way round, which is
1690        // what a real server does with a table nothing has been written to yet.
1691        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
1709/// `CLUSTER MIGRATION IMPORT|STATUS|CANCEL`, which is the newer way of moving a
1710/// slot range: the importing node drives the whole move rather than a tool
1711/// stepping it key by key.
1712///
1713/// `STATUS` and `CANCEL` report and cancel what is really running, which on this
1714/// node is a migration another node started against it. Starting one from here,
1715/// which is what `IMPORT` is, is the other side of the protocol and is D-149.
1716fn 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            // A cancelled handoff is not one whose write pause anybody should
1739            // have to wait out.
1740            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    // An embedded server has no bus, nobody to dial and nobody to tell, so there
1750    // is nothing an import could even mean on one. Checked after the slot ranges
1751    // rather than before, so that a caller who got the ranges wrong is told about
1752    // the ranges wherever the command was sent.
1753    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
1773/// Who a set of slot ranges would have to come from, which is the reference's
1774/// `validateImportSlotRanges` and the owner check behind it.
1775///
1776/// In the reference's order, because the order is what a caller reads to find
1777/// out which of several things it got wrong. The last of them is the one an
1778/// operator hits most: asking a node to import slots it already has.
1779fn 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    // The two ways of moving a slot do not mix, the same as on the giving up
1788    // side: a slot half way through the old one has keys on two nodes at once.
1789    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
1829/// The list of slot ranges the tail of an argument list spells out, which is the
1830/// reference's `parseSlotRangesOrReply` and the validation behind it.
1831///
1832/// Sorted and with the ranges that touch joined up, because that is what the
1833/// reference hands back and the two checks after it are made against the joined
1834/// up list rather than what the caller typed. Ranges that touch join and ranges
1835/// that overlap do not, which is what makes `1 2 2 3` a slot named twice while
1836/// `1 2 3 4` is one range of four.
1837fn 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
1882/// `TRIMSLOTS`, which drops the keys of slot ranges this node does not serve.
1883///
1884/// Not a command an operator has any reason to type, and it is a top level
1885/// command rather than a `CLUSTER` subcommand because of where it is sent from:
1886/// a node that has just handed slots over writes one of these to its replicas
1887/// and its append only file, so that a replica drops the same keys its master
1888/// just dropped rather than going on answering for data that has moved. One
1889/// command for the ranges rather than a deletion per key, because a slot range
1890/// can hold millions of keys and the far side can work the list out for itself.
1891///
1892/// The refusal in the middle is the one that matters. A node will not empty a
1893/// slot it is serving, whatever it is told, so a `TRIMSLOTS` that arrives late
1894/// or names the wrong range cannot take live data with it. A replica does not
1895/// make that check, because the slots are its master's and not its own and it is
1896/// being told what its master already did.
1897pub(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
1929/// `CLUSTER SYNCSLOTS`, which is what two nodes say to each other while a slot
1930/// range moves from one to the other under atomic slot migration.
1931///
1932/// Not a command an operator ever types. The node taking the slots opens an
1933/// ordinary client connection to the node giving them up, logs in as the cluster
1934/// rather than as a user, and drives the whole move over that connection and one
1935/// more like it. So every arm here answers another node running the same code,
1936/// which is why the reference does not defend the state machine against being
1937/// driven out of order and why it hangs up on anybody who is not a node rather
1938/// than just saying no. Getting to the point of being able to send this is the
1939/// hard part and the refusal makes it worthless.
1940///
1941/// What is in so far is the gate, the argument checking and the two arms that
1942/// have a real answer on a node with no migration running, which is every node
1943/// today. Starting a migration is D-149.
1944fn syncslots(server: &Server, session: &mut Session, args: Args<'_>, out: &mut Out) -> Result<()> {
1945    if !session.internal() {
1946        // The hang up is the reference's and it is worth copying. The sentence on
1947        // its own would be enough to be correct, and dropping the connection is
1948        // what makes sitting there guessing at the protocol expensive.
1949        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        // A replica owns nothing, so there is nothing anybody could be asking it
1958        // to hand over. The one thing it does take is `CONF`, and only from its
1959        // own master, which is how it hears about the migration its master is in
1960        // the middle of. Anything else from the master is dropped in silence
1961        // rather than refused, because an error written into the replication
1962        // stream is an error nobody reads.
1963        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        // Both of these say a transfer has ended, and on a node with no transfer
1982        // running the reference logs that it was not expecting them and drops the
1983        // connection without writing anything back.
1984        session.hang_up();
1985        return Ok(());
1986    }
1987    if args::is(action, b"ack") && args.len() == 5 {
1988        // The arm that answers nothing. This connection's other direction is the
1989        // change stream, and anything written back on it would be read as a
1990        // command, so a bad state word or a number that is not one is dropped in
1991        // silence rather than refused.
1992        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        // The other arm that never answers. `FAIL` does nothing at all on the
2001        // reference either: it is there so that the far side has something to
2002        // send that will not come back as a syntax error.
2003        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
2011/// `CLUSTER SYNCSLOTS SYNC <task-id> <start> <end> [<start> <end> ...]`, which is
2012/// the node taking the slots asking for them.
2013///
2014/// Every check the reference makes before it starts is made here, in its order,
2015/// because they are what a caller reads to find out it asked the wrong node.
2016///
2017/// What comes back on the way through is `+RDBCHANNELSYNCSLOTS`, which is the far
2018/// side being told to open the second connection the snapshot will come down.
2019/// Nothing else is written to this connection yet: it is the one the changes
2020/// since the snapshot would go down, and those are D-149.
2021fn 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        // The two ways of moving a slot do not mix. A slot that is half way
2029        // through the old one has keys on two nodes at once and a snapshot of it
2030        // would be a snapshot of half the data.
2031        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        // A node that is not in the table, or that is somebody's replica, has
2059        // nowhere to put a slot. The far side only says who it is when it sent
2060        // `CONF NODE-ID` first, and the reference lets a connection that did not
2061        // through, so this is checked only when there is something to check.
2062        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
2082/// `CLUSTER SYNCSLOTS RDBCHANNEL <task-id>`, which is the second connection of a
2083/// migration arriving to be handed the snapshot.
2084///
2085/// The reference answers `+SLOTSSNAPSHOT` and then forks, and the snapshot goes
2086/// out of the child while the parent carries on serving. There is no fork here,
2087/// so the snapshot is built with every write on the server held off and then
2088/// written, which is the same trade a full resync makes and is why the snapshot
2089/// is one slot range rather than the whole keyspace.
2090fn 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
2101/// `CLUSTER SYNCSLOTS CONF <option> <value> [<option> <value> ...]`, which is
2102/// each side telling the other something it will need in a moment.
2103///
2104/// Three of the four options are the node that is talking, a hint about how big a
2105/// slot is so the receiving side can size its tables once instead of growing them
2106/// all the way up, and a capability word that is ignored on purpose so that a
2107/// newer node can say something an older one has never heard of. The fourth is a
2108/// master handing its own replicas the state of the migration it is in.
2109///
2110/// Every complaint here is written rather than returned, because the reference
2111/// keeps going after an option it did not understand and still says `OK` at the
2112/// end, so one command can answer with both an error line and an `OK`. Returning
2113/// would throw the first of those away.
2114fn 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            // Who is on the other end of this connection, which is what the
2125            // `SYNC` after it is checked against and what the task it starts
2126            // records as the node the slots are going to.
2127            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            // A replica that hears this is being told to follow along with a
2172            // migration its master is running, and there is nothing here to
2173            // follow along with yet, so it says so and carries on with the rest
2174            // of the options exactly as the reference does.
2175            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
2200/// Whether a `slot-info` value is one, which is `slot:key-count:expire-count`
2201/// with all three of them numbers and the first of them a slot.
2202///
2203/// What it is for is sizing: the node about to receive a slot is being told how
2204/// many keys are coming so that it can make room for them in one go. Nothing is
2205/// sized on it here, because the tables this engine keeps a slot's keys in grow
2206/// without the rehash a size hint exists to avoid, so the value is only checked.
2207fn 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
2221/// `CLUSTER SETSLOT <slot> IMPORTING|MIGRATING|STABLE|NODE`.
2222///
2223/// The four arms and the order of their checks are the reference's, which is
2224/// worth being exact about because a resharding tool drives this and reads the
2225/// sentences it gets back.
2226///
2227/// `NODE` is where a slot migration ends and it is the one arm that does more
2228/// than move a field. When the node being named is this one and this node was
2229/// importing the slot, the config epoch goes up and the whole cluster is told
2230/// straight away, because until that happens every other node is still pointing
2231/// clients at the node the slot came from and the higher epoch is the only thing
2232/// that makes them stop.
2233fn setslot(server: &Server, session_db: usize, args: Args<'_>, out: &mut Out) -> Result<()> {
2234    // A replica has no say in who owns what, so this is refused before the slot
2235    // number is even looked at.
2236    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    // A slot an atomic migration is moving is not one to hand over by hand. The
2244    // two ways of moving a slot write the same three fields from two directions,
2245    // and the migration would finish by claiming a slot the operator had already
2246    // given to somebody else. Cancelling the task is the way out and the sentence
2247    // says so, because a resharding tool is what reads it.
2248    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    // Set when the `NODE` arm decides the rest of the cluster has to be told
2266    // now rather than at the next ping. Done once the lock is gone, since
2267    // building the packet takes the same lock.
2268    let mut announce = false;
2269    // And when this node has just given its last slot away and should follow
2270    // whoever took it. The same, and for the same reason.
2271    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            // The keys are counted twice here rather than once, which reads
2309            // oddly and is the reference's own shape. The first count refuses
2310            // handing the slot to somebody else while this node is still
2311            // holding keys for it, since that would leave two nodes answering
2312            // for the same data. The second clears the migrating mark, and it
2313            // is a separate question because a slot can be empty and marked
2314            // whether or not it was ever this node's.
2315            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            // A master that has just handed over its last slot follows whoever
2329            // took it, which is what stops a resharded cluster being left with
2330            // a node that owns nothing and serves nobody.
2331            if was_mine && to != 0 && map.runs(0).is_empty() {
2332                follow = Some(to);
2333            }
2334            // The import is finished, so the epoch goes up and everybody is
2335            // told. Nothing else in this function moves the epoch, because
2336            // nothing else is this node claiming something that used to be
2337            // somebody else's.
2338            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    // Only on a real server. An embedded one has no bus and nobody to follow,
2350    // and the slot still changes hands the same way.
2351    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
2363/// How many keys of a slot are here, which two of `SETSLOT NODE`'s refusals turn
2364/// on and which `CLUSTER COUNTKEYSINSLOT` answers.
2365fn 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
2375/// The reference's `clusterBumpConfigEpochWithoutConsensus`.
2376///
2377/// A node that has just taken a slot off somebody needs an epoch higher than
2378/// theirs, or every other node will keep the old owner: a higher epoch is the
2379/// whole of how the cluster decides which of two claims on a slot is the newer
2380/// one. Without consensus means exactly that, nobody is asked, and the comment
2381/// in the reference is worth repeating: two nodes can end up on the same epoch
2382/// this way, and the collision rule sorts that out afterwards rather than the
2383/// bump trying to avoid it.
2384///
2385/// It does nothing at all when this node already holds the largest epoch
2386/// anybody has, since there is nothing left to outrank. `false` is that case,
2387/// and it is what `CLUSTER BUMPEPOCH` answers `STILL` for.
2388fn 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
2404/// Claim slot ranges for this node, which is the last step of an import and the
2405/// reference's `ASM_EVENT_TAKEOVER`.
2406///
2407/// Nobody is asked. The epoch goes up without consensus and the claim goes out
2408/// on the bus, and a higher epoch is the whole of how every other node decides
2409/// which of two claims on a slot is the newer one. That is safe here for the
2410/// reason it is not safe in general: the node that used to own these slots has
2411/// stopped taking writes for them and has said there is nothing more coming, so
2412/// there is no second writer to disagree with.
2413///
2414/// The old owner finds out the same way everybody else does, from the bus, and
2415/// what it does about it is the other half of this, in `asm_slots_moved`.
2416fn 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
2433/// `CLUSTER FLUSHSLOTS`, which drops every slot this node claims.
2434fn 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
2457/// `CLUSTER BUMPEPOCH`, which takes an epoch above everybody else's.
2458///
2459/// Answers `BUMPED` and the new epoch when it moved and `STILL` and the current
2460/// one when there was nothing to outrank, which is what a second call in a row
2461/// gets. See [`bump_without_consensus`] for why doing nothing is the right
2462/// answer rather than a wasted epoch.
2463fn 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
2479/// `CLUSTER SET-CONFIG-EPOCH`, which is how a brand new cluster gives each node
2480/// a different epoch before anything has been agreed.
2481fn 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
2506/// `CLUSTER RESET [HARD|SOFT]`, which gives up every slot and, when hard, takes
2507/// a new identity as well.
2508fn 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        // A reset makes this node a master of nothing again, which is the half
2531        // of it a soft reset does as well: it stops following anybody and it
2532        // forgets everybody, it just keeps its name.
2533        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
2551// -------------------------------------------------------------- the key sets
2552
2553/// `CLUSTER COUNTKEYSINSLOT`.
2554fn 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
2570/// `CLUSTER GETKEYSINSLOT`.
2571fn 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
2592// ------------------------------------------------------------- the disk copy
2593
2594/// Write the table where a restart will find it.
2595///
2596/// The format is the reference's, line for line, so a node's file can be read by
2597/// a real server and the other way round. The write goes to a temporary name and
2598/// is renamed over the real one, the same way the snapshot writer does it, so a
2599/// reader never sees half a file.
2600fn 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    // The vote goes in the file because it has to outlive the process. A node
2610    // that voted, restarted and voted again in the same epoch would have voted
2611    // twice, and two votes from one master is how two replicas of the same
2612    // master both come away believing they won.
2613    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    /// Read the table back off disk, which is what makes a restart a restart
2635    /// rather than a node that has forgotten which half of the keyspace is its.
2636    ///
2637    /// A file that is not there is not an error, because the first start of a
2638    /// brand new node has none. A file that is there and is nonsense is, because
2639    /// the alternative is a node that comes up owning nothing and starts
2640    /// answering `CLUSTERDOWN` for keys it has on disk.
2641    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    /// The parse, split out so that a test can hand it text without a file.
2657    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        // Slot runs are kept against the owner's id and not against its position
2662        // in the list, because this node moves to the front of the list when its
2663        // own line turns up and every position recorded before that moves down.
2664        let mut owned: Vec<(String, u16, u16)> = Vec::new();
2665        // The master field names a node that may not have been read yet, so
2666        // both ends are kept as ids and resolved once every line is in.
2667        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                // `vars currentEpoch <n> lastVoteEpoch <n>`, read as pairs so
2677                // that a build which adds a third one does not break the parse.
2678                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            // The link state field, which is read past rather than read.
2704            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                // The migrating and importing markers are re-read from the
2709                // brackets below rather than here, since they name a node that
2710                // may not have been read yet.
2711                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                // The two timestamps come back as now rather than as what the
2759                // file says, because a file written an hour ago would otherwise
2760                // put every node in it straight past the failure timeout. All
2761                // the file is really recording is whether a ping was in flight.
2762                ping_sent: stamp(ping, now),
2763                pong_recv: stamp(pong, now),
2764                data_recv: now,
2765                fail_time: 0,
2766                offset: 0,
2767                // The link state in the file is written for a human reading it
2768                // and is not read back. There is no link to anybody at startup,
2769                // and saying otherwise would stop the bus ever dialling out.
2770                linked: false,
2771                // Not in the file, which is the reference's shape as well: the
2772                // epoch this node last voted in is, and that is the check that
2773                // has to survive a restart. This one only spaces two elections
2774                // out and a node that has just started has not held one.
2775                voted_time: 0,
2776                reports: Vec::new(),
2777            };
2778            if named.split(',').any(|f| f == "myself") {
2779                // This node goes first, and everything already read moves down.
2780                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            // A replica's config epoch is its master's business, so the file's
2805            // value is dropped rather than believed. Keeping it would let a
2806            // replica that was a master an hour ago outvote the node that took
2807            // its slots the moment it came back.
2808            map.nodes[usize::from(who)].epoch = 0;
2809        }
2810        *self.cluster.map.lock() = map;
2811        Ok(())
2812    }
2813}
2814
2815/// One of the two timestamps out of the node table, read as a yes or a no.
2816///
2817/// The reference does the same thing for the same reason. The number in the
2818/// file was taken from a clock that has since moved on, and all the reader can
2819/// usefully learn from it is whether there was a ping in flight when the file
2820/// was written, so a number that is not zero comes back as the time now.
2821fn stamp(field: &str, now: u64) -> u64 {
2822    match field.parse::<u64>() {
2823        Ok(0) | Err(_) => 0,
2824        Ok(_) => now,
2825    }
2826}
2827
2828/// Pull the host, the port and the shard id out of one `ip:port@bus,aux` field.
2829///
2830/// The aux fields after the first comma are a list of `name=value` pairs with an
2831/// optional hostname in front of them, and a build that adds one has to leave a
2832/// build that does not able to read the file, so everything but the shard id is
2833/// skipped rather than being counted.
2834fn 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// ------------------------------------------------------- the hand filled map
2857
2858/// What a test uses to fill the table the bus will fill later.
2859///
2860/// A single node cannot make a redirection happen: it owns every slot or it
2861/// owns none, and either way there is nowhere to send anybody. So the tests for
2862/// `MOVED`, `ASK` and `TRYAGAIN` put a second node in the table by hand, which
2863/// is exactly the state the bus will produce and is the state the config file
2864/// parser already reads.
2865#[cfg(test)]
2866impl Server {
2867    /// Take every slot, and be up about it straight away.
2868    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    /// Put a node in the table that nobody has met, and answer its index.
2881    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    /// Hand one slot to another node.
2898    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    /// Make this node a replica of another one in the table.
2904    ///
2905    /// What `CLUSTER REPLICATE` does to the table, without the half of it that
2906    /// starts replicating, which an embedded server has no way of doing and
2907    /// which none of the tests that want a replica in the table care about.
2908    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    /// Mark one slot as on its way out to, or in from, another node.
2916    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// ---------------------------------------------------------------- the tests
2924
2925#[cfg(test)]
2926mod tests {
2927    use super::{SLOTS, key_slot};
2928
2929    /// The two ways of moving a slot write the same three fields, so a slot an
2930    /// atomic migration is moving is refused to the old way by name.
2931    ///
2932    /// The sentence is compared against the one a real 8.10.1 sends, because a
2933    /// resharding tool is what reads it and the way out of the situation is
2934    /// named in it.
2935    #[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        // Nothing is moving, so the old way works.
2973        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        // Either side of the range is untouched, since only the slots actually
2992        // moving are the ones there are two writers for.
2993        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        // And the gate goes when the task does.
3002        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    /// The slots the reference answered for these keys, read off a running
3010    /// 8.10.1 with cluster mode on rather than worked out from the algorithm.
3011    #[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    /// The hash tag is the whole reason a command can name two keys at all, and
3020    /// its edge cases are the ones a client library gets wrong.
3021    #[test]
3022    fn the_hash_tag_rules_are_the_reference_rules() {
3023        // Two keys with the same tag are one slot, whatever else they say.
3024        assert_eq!(
3025            key_slot(b"{user1000}.following"),
3026            key_slot(b"{user1000}.followers")
3027        );
3028        // A tag with nothing in it is not a tag.
3029        assert_eq!(key_slot(b"{}foo"), key_slot(b"{}foo"));
3030        assert_ne!(key_slot(b"{}foo"), key_slot(b"foo"));
3031        // An opening brace with no closing one is not a tag either.
3032        assert_ne!(key_slot(b"{foo"), key_slot(b"foo"));
3033        // Only the first closing brace after the first opening one counts.
3034        assert_eq!(key_slot(b"{a}{b}"), key_slot(b"a"));
3035        // A tag can hold a brace, since the search is for the first close.
3036        assert_eq!(key_slot(b"foo{{bar}}zap"), key_slot(b"{bar"));
3037    }
3038
3039    /// Every slot is reachable and none is out of range, which is the only
3040    /// property the command path actually leans on.
3041    #[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    /// Reading the node table back has to put every run of slots on the node
3054    /// that owns it, including when this node's own line is not the first one.
3055    ///
3056    /// It is worth a test of its own because the failure is silent and only
3057    /// shows up on a cluster with three nodes in it: this node moves to the
3058    /// front of the list when its line is read, everything read before it moves
3059    /// down one, and a run recorded against a position rather than against an
3060    /// id ends up on the wrong node.
3061    #[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    /// Three fields in the node table say what was true when it was written and
3088    /// not what is true now, and reading them back as if they were live state
3089    /// is what stops a restarted node ever dialling anybody again.
3090    #[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        // A ping that was in flight comes back as one sent now, and a ping that
3105        // was not stays at zero, which is the difference the cron reads.
3106        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        // And a replica's config epoch belongs to its master, not to the file.
3110        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}