2023-03-14 17:38:21 +01:00
|
|
|
|
use std::net::ToSocketAddrs;
|
2023-03-21 18:25:53 +01:00
|
|
|
|
use std::str::FromStr;
|
2023-03-20 18:13:39 +01:00
|
|
|
|
use std::sync::{Arc, RwLock};
|
2023-03-14 17:38:21 +01:00
|
|
|
|
|
2023-03-16 16:31:16 +01:00
|
|
|
|
use batch::Batch;
|
2023-03-16 18:53:22 +01:00
|
|
|
|
use crossbeam::channel::{unbounded, Receiver, Sender};
|
2023-03-23 12:28:36 +01:00
|
|
|
|
use ductile::{connect_channel, connect_channel_with_enc, ChannelReceiver, ChannelSender};
|
|
|
|
|
use log::{info, warn};
|
2023-03-22 15:49:35 +01:00
|
|
|
|
use meilisearch_types::keys::Key;
|
2023-03-16 18:53:22 +01:00
|
|
|
|
use meilisearch_types::tasks::{KindWithContent, Task};
|
2023-03-14 17:38:21 +01:00
|
|
|
|
use serde::{Deserialize, Serialize};
|
|
|
|
|
|
2023-03-16 16:31:16 +01:00
|
|
|
|
pub mod batch;
|
2023-03-14 17:38:21 +01:00
|
|
|
|
mod leader;
|
|
|
|
|
|
|
|
|
|
pub use leader::Leader;
|
2023-03-22 15:49:35 +01:00
|
|
|
|
use uuid::Uuid;
|
2023-03-14 17:38:21 +01:00
|
|
|
|
|
|
|
|
|
#[derive(Debug, thiserror::Error)]
|
|
|
|
|
pub enum Error {
|
|
|
|
|
#[error("Network issue occured")]
|
|
|
|
|
NetworkIssue,
|
|
|
|
|
#[error("Internal error: {0}")]
|
|
|
|
|
SerdeJson(#[from] serde_json::Error),
|
|
|
|
|
}
|
|
|
|
|
|
2023-03-16 14:31:03 +01:00
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
2023-03-14 17:38:21 +01:00
|
|
|
|
pub enum LeaderMsg {
|
2023-03-22 15:49:35 +01:00
|
|
|
|
/// A dump to join the cluster
|
2023-03-21 17:56:18 +01:00
|
|
|
|
JoinFromDump(Vec<u8>),
|
2023-03-22 15:49:35 +01:00
|
|
|
|
/// Starts a new batch
|
2023-03-16 16:31:16 +01:00
|
|
|
|
StartBatch { id: u32, batch: Batch },
|
2023-03-22 15:49:35 +01:00
|
|
|
|
/// Tell the follower to commit the update asap
|
2023-03-14 17:38:21 +01:00
|
|
|
|
Commit(u32),
|
2023-03-22 15:49:35 +01:00
|
|
|
|
/// Tell the follower to commit the update asap
|
2023-03-16 18:53:22 +01:00
|
|
|
|
RegisterNewTask { task: Task, update_file: Option<Vec<u8>> },
|
2023-03-22 15:49:35 +01:00
|
|
|
|
|
|
|
|
|
/// Tell the follower to commit the update asap
|
|
|
|
|
ApiKeyOperation(ApiKeyOperation),
|
2023-03-14 17:38:21 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
|
|
|
pub enum FollowerMsg {
|
|
|
|
|
// Let the leader knows you're ready to commit
|
|
|
|
|
ReadyToCommit(u32),
|
|
|
|
|
RegisterNewTask(KindWithContent),
|
|
|
|
|
}
|
|
|
|
|
|
2023-03-21 18:25:53 +01:00
|
|
|
|
#[derive(Default, Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
|
2023-03-22 14:24:53 +01:00
|
|
|
|
#[serde(rename_all = "lowercase")]
|
2023-03-14 17:38:21 +01:00
|
|
|
|
pub enum Consistency {
|
|
|
|
|
One,
|
|
|
|
|
Two,
|
|
|
|
|
Quorum,
|
2023-03-22 14:24:53 +01:00
|
|
|
|
#[default]
|
2023-03-14 17:38:21 +01:00
|
|
|
|
All,
|
|
|
|
|
}
|
|
|
|
|
|
2023-03-22 15:49:35 +01:00
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
|
|
|
|
pub enum ApiKeyOperation {
|
|
|
|
|
Insert(Key),
|
|
|
|
|
Delete(Uuid),
|
|
|
|
|
}
|
|
|
|
|
|
2023-03-21 18:25:53 +01:00
|
|
|
|
impl std::fmt::Display for Consistency {
|
|
|
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
2023-03-22 14:24:53 +01:00
|
|
|
|
match self {
|
|
|
|
|
Consistency::One => write!(f, "one"),
|
|
|
|
|
Consistency::Two => write!(f, "two"),
|
|
|
|
|
Consistency::Quorum => write!(f, "quorum"),
|
|
|
|
|
Consistency::All => write!(f, "all"),
|
|
|
|
|
}
|
2023-03-21 18:25:53 +01:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl FromStr for Consistency {
|
2023-03-22 14:24:53 +01:00
|
|
|
|
type Err = String;
|
2023-03-21 18:25:53 +01:00
|
|
|
|
|
|
|
|
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
2023-03-22 14:24:53 +01:00
|
|
|
|
match s {
|
|
|
|
|
"one" => Ok(Consistency::One),
|
|
|
|
|
"two" => Ok(Consistency::Two),
|
|
|
|
|
"quorum" => Ok(Consistency::Quorum),
|
|
|
|
|
"all" => Ok(Consistency::All),
|
|
|
|
|
s => Err(format!(
|
|
|
|
|
"Unexpected value `{s}`, expected one of `one`, `two`, `quorum`, `all`"
|
|
|
|
|
)),
|
|
|
|
|
}
|
2023-03-21 18:25:53 +01:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-03-22 14:24:53 +01:00
|
|
|
|
#[derive(Clone)]
|
|
|
|
|
pub enum Cluster {
|
|
|
|
|
Leader(Leader),
|
|
|
|
|
Follower(Follower),
|
|
|
|
|
}
|
|
|
|
|
|
2023-03-16 18:53:22 +01:00
|
|
|
|
#[derive(Clone)]
|
2023-03-14 17:38:21 +01:00
|
|
|
|
pub struct Follower {
|
|
|
|
|
sender: ChannelSender<FollowerMsg>,
|
2023-03-16 18:53:22 +01:00
|
|
|
|
|
|
|
|
|
get_batch: Receiver<(u32, Batch)>,
|
|
|
|
|
must_commit: Receiver<u32>,
|
|
|
|
|
register_new_task: Receiver<(Task, Option<Vec<u8>>)>,
|
|
|
|
|
|
2023-03-22 15:49:35 +01:00
|
|
|
|
api_key_op: Receiver<ApiKeyOperation>,
|
|
|
|
|
|
2023-03-20 18:13:39 +01:00
|
|
|
|
batch_id: Arc<RwLock<u32>>,
|
2023-03-14 17:38:21 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl Follower {
|
2023-03-23 12:28:36 +01:00
|
|
|
|
pub fn join(leader: impl ToSocketAddrs, master_key: Option<String>) -> (Follower, Vec<u8>) {
|
|
|
|
|
let (sender, receiver) = if let Some(master_key) = master_key {
|
|
|
|
|
let mut enc = [0; 32];
|
|
|
|
|
let master_key = master_key.as_bytes();
|
|
|
|
|
if master_key.len() < 32 {
|
|
|
|
|
warn!("Master key is not secure, use a longer master key (at least 32 bytes long)");
|
|
|
|
|
}
|
|
|
|
|
enc.iter_mut().zip(master_key).for_each(|(enc, mk)| *enc = *mk);
|
|
|
|
|
info!("Connecting with encryption enabled");
|
|
|
|
|
connect_channel_with_enc(leader, &enc).unwrap()
|
|
|
|
|
} else {
|
|
|
|
|
connect_channel(leader).unwrap()
|
|
|
|
|
};
|
2023-03-16 18:53:22 +01:00
|
|
|
|
|
|
|
|
|
info!("Connection to the leader established");
|
|
|
|
|
|
2023-03-21 17:56:18 +01:00
|
|
|
|
info!("Waiting for the leader to contact us");
|
|
|
|
|
let state = receiver.recv().unwrap();
|
|
|
|
|
|
|
|
|
|
let dump = match state {
|
|
|
|
|
LeaderMsg::JoinFromDump(dump) => dump,
|
|
|
|
|
msg => panic!("Received unexpected message {msg:?}"),
|
|
|
|
|
};
|
|
|
|
|
|
2023-03-16 18:53:22 +01:00
|
|
|
|
let (get_batch_sender, get_batch_receiver) = unbounded();
|
|
|
|
|
let (must_commit_sender, must_commit_receiver) = unbounded();
|
|
|
|
|
let (register_task_sender, register_task_receiver) = unbounded();
|
2023-03-22 15:49:35 +01:00
|
|
|
|
let (create_api_key_sender, create_api_key_receiver) = unbounded();
|
2023-03-16 18:53:22 +01:00
|
|
|
|
|
|
|
|
|
std::thread::spawn(move || {
|
2023-03-22 15:49:35 +01:00
|
|
|
|
Self::router(
|
|
|
|
|
receiver,
|
|
|
|
|
get_batch_sender,
|
|
|
|
|
must_commit_sender,
|
|
|
|
|
register_task_sender,
|
|
|
|
|
create_api_key_sender,
|
|
|
|
|
);
|
2023-03-16 18:53:22 +01:00
|
|
|
|
});
|
|
|
|
|
|
2023-03-21 17:56:18 +01:00
|
|
|
|
(
|
|
|
|
|
Follower {
|
|
|
|
|
sender,
|
|
|
|
|
get_batch: get_batch_receiver,
|
|
|
|
|
must_commit: must_commit_receiver,
|
|
|
|
|
register_new_task: register_task_receiver,
|
2023-03-22 15:49:35 +01:00
|
|
|
|
api_key_op: create_api_key_receiver,
|
2023-03-21 17:56:18 +01:00
|
|
|
|
batch_id: Arc::default(),
|
|
|
|
|
},
|
|
|
|
|
dump,
|
|
|
|
|
)
|
2023-03-14 17:38:21 +01:00
|
|
|
|
}
|
|
|
|
|
|
2023-03-16 18:53:22 +01:00
|
|
|
|
fn router(
|
|
|
|
|
receiver: ChannelReceiver<LeaderMsg>,
|
|
|
|
|
get_batch: Sender<(u32, Batch)>,
|
|
|
|
|
must_commit: Sender<u32>,
|
|
|
|
|
register_new_task: Sender<(Task, Option<Vec<u8>>)>,
|
2023-03-22 15:49:35 +01:00
|
|
|
|
api_key_op: Sender<ApiKeyOperation>,
|
2023-03-16 18:53:22 +01:00
|
|
|
|
) {
|
2023-03-14 17:38:21 +01:00
|
|
|
|
loop {
|
2023-03-16 18:53:22 +01:00
|
|
|
|
match receiver.recv().expect("Lost connection to the leader") {
|
2023-03-21 17:56:18 +01:00
|
|
|
|
LeaderMsg::JoinFromDump(_) => {
|
2023-03-23 12:28:36 +01:00
|
|
|
|
warn!("Received a join from dump msg but I’m already running : ignoring the message")
|
2023-03-21 17:56:18 +01:00
|
|
|
|
}
|
2023-03-16 18:53:22 +01:00
|
|
|
|
LeaderMsg::StartBatch { id, batch } => {
|
|
|
|
|
info!("Starting to process a new batch");
|
|
|
|
|
get_batch.send((id, batch)).expect("Lost connection to the main thread")
|
|
|
|
|
}
|
|
|
|
|
LeaderMsg::Commit(id) => {
|
|
|
|
|
info!("Must commit");
|
|
|
|
|
must_commit.send(id).expect("Lost connection to the main thread")
|
|
|
|
|
}
|
|
|
|
|
LeaderMsg::RegisterNewTask { task, update_file } => {
|
|
|
|
|
info!("Registered a new task");
|
|
|
|
|
register_new_task
|
|
|
|
|
.send((task, update_file))
|
|
|
|
|
.expect("Lost connection to the main thread")
|
2023-03-14 17:38:21 +01:00
|
|
|
|
}
|
2023-03-22 15:49:35 +01:00
|
|
|
|
LeaderMsg::ApiKeyOperation(key) => {
|
|
|
|
|
api_key_op.send(key).expect("Lost connection to the main thread")
|
|
|
|
|
}
|
2023-03-14 17:38:21 +01:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-03-20 18:13:39 +01:00
|
|
|
|
pub fn get_new_batch(&self) -> Batch {
|
2023-03-16 19:02:54 +01:00
|
|
|
|
info!("Get new batch called");
|
2023-03-16 18:53:22 +01:00
|
|
|
|
let (id, batch) = self.get_batch.recv().expect("Lost connection to the leader");
|
2023-03-16 19:02:54 +01:00
|
|
|
|
info!("Got a new batch");
|
2023-03-20 18:13:39 +01:00
|
|
|
|
*self.batch_id.write().unwrap() = id;
|
2023-03-16 18:53:22 +01:00
|
|
|
|
batch
|
|
|
|
|
}
|
|
|
|
|
|
2023-03-20 18:13:39 +01:00
|
|
|
|
pub fn ready_to_commit(&self) {
|
2023-03-16 19:02:54 +01:00
|
|
|
|
info!("I'm ready to commit");
|
2023-03-20 18:13:39 +01:00
|
|
|
|
let batch_id = self.batch_id.read().unwrap();
|
|
|
|
|
|
|
|
|
|
self.sender.send(FollowerMsg::ReadyToCommit(*batch_id)).unwrap();
|
2023-03-14 17:38:21 +01:00
|
|
|
|
|
|
|
|
|
loop {
|
2023-03-16 18:53:22 +01:00
|
|
|
|
let id = self.must_commit.recv().expect("Lost connection to the leader");
|
|
|
|
|
#[allow(clippy::comparison_chain)]
|
2023-03-20 18:13:39 +01:00
|
|
|
|
if id == *batch_id {
|
2023-03-16 18:53:22 +01:00
|
|
|
|
break;
|
2023-03-20 18:13:39 +01:00
|
|
|
|
} else if id > *batch_id {
|
2023-03-16 18:53:22 +01:00
|
|
|
|
panic!("We missed a batch");
|
2023-03-14 17:38:21 +01:00
|
|
|
|
}
|
|
|
|
|
}
|
2023-03-16 19:02:54 +01:00
|
|
|
|
info!("I got the right to commit");
|
2023-03-14 17:38:21 +01:00
|
|
|
|
}
|
2023-03-16 18:53:22 +01:00
|
|
|
|
|
2023-03-20 18:13:39 +01:00
|
|
|
|
pub fn get_new_task(&self) -> (Task, Option<Vec<u8>>) {
|
2023-03-22 15:49:35 +01:00
|
|
|
|
self.register_new_task.recv().expect("Lost connection to the leader")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn api_key_operation(&self) -> ApiKeyOperation {
|
|
|
|
|
info!("Creating a new api key");
|
|
|
|
|
self.api_key_op.recv().expect("Lost connection to the leader")
|
2023-03-16 18:53:22 +01:00
|
|
|
|
}
|
2023-03-14 17:38:21 +01:00
|
|
|
|
}
|