diff --git a/Cargo.lock b/Cargo.lock index afd5bf59..1b47f753 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -373,6 +373,15 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" +[[package]] +name = "autotools" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef941527c41b0fc0dd48511a8154cd5fc7e29200a0ff8b7203c5d777dbc795cf" +dependencies = [ + "cc", +] + [[package]] name = "axum" version = "0.6.20" @@ -700,6 +709,46 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3fd119d74b830634cea2a0f58bbd0d54540518a14397557951e79340abc28c0" +[[package]] +name = "config" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7328b20597b53c2454f0b1919720c25c7339051c02b72b7e05409e00b14132be" +dependencies = [ + "async-trait", + "convert_case 0.6.0", + "json5", + "lazy_static", + "nom", + "pathdiff", + "ron", + "rust-ini", + "serde", + "serde_json", + "toml 0.8.19", + "yaml-rust", +] + +[[package]] +name = "const-random" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87e00182fe74b066627d63b85fd550ac2998d4b0bd86bfed477a0ae4c7c71359" +dependencies = [ + "const-random-macro", +] + +[[package]] +name = "const-random-macro" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9d839f2a20b0aee515dc581a6172f2321f96cab76c1a38a4c584a194955390e" +dependencies = [ + "getrandom", + "once_cell", + "tiny-keccak", +] + [[package]] name = "const_format" version = "0.2.33" @@ -726,6 +775,15 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" +[[package]] +name = "convert_case" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec182b0ca2f35d8fc196cf3404988fd8b8c739a4d270ff118a398feb0cbec1ca" +dependencies = [ + "unicode-segmentation", +] + [[package]] name = "cookie" version = "0.16.2" @@ -785,6 +843,12 @@ version = "0.8.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" +[[package]] +name = "crunchy" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" + [[package]] name = "crypto-common" version = "0.1.6" @@ -810,7 +874,7 @@ version = "0.99.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5f33878137e4dafd7fa914ad4e259e18a4e8e532b9617a2d0150262bf53abfce" dependencies = [ - "convert_case", + "convert_case 0.4.0", "proc-macro2", "quote", "rustc_version", @@ -848,6 +912,15 @@ dependencies = [ "winapi", ] +[[package]] +name = "dlv-list" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "442039f5147480ba31067cb00ada1adae6892028e40e45fc5de7b7df6dcc1b5f" +dependencies = [ + "const-random", +] + [[package]] name = "doxygen-rs" version = "0.4.2" @@ -1121,6 +1194,12 @@ version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +[[package]] +name = "hashbrown" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e" + [[package]] name = "hashbrown" version = "0.14.5" @@ -1388,9 +1467,7 @@ dependencies = [ [[package]] name = "jopemachine-raft" -version = "0.7.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57206b3af8ccd4a755cac94abb0f0692544dfb10869f64dbc19b69ab73c6e251" +version = "0.7.13" dependencies = [ "bytes", "fxhash", @@ -1399,6 +1476,7 @@ dependencies = [ "protobuf", "raft-proto", "rand", + "serde", "slog", "slog-envlogger", "slog-stdlog", @@ -1415,6 +1493,17 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "json5" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96b0db21af676c1ce64250b5f40f3ce2cf27e4e47cb91ed91eb6fe9350b430c1" +dependencies = [ + "pest", + "pest_derive", + "serde", +] + [[package]] name = "language-tags" version = "0.3.2" @@ -1485,6 +1574,12 @@ dependencies = [ "vcpkg", ] +[[package]] +name = "linked-hash-map" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" + [[package]] name = "linux-raw-sys" version = "0.4.14" @@ -1691,6 +1786,16 @@ version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" +[[package]] +name = "ordered-multimap" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ed8acf08e98e744e5384c8bc63ceb0364e68a6854187221c18df61c4797690e" +dependencies = [ + "dlv-list", + "hashbrown 0.13.2", +] + [[package]] name = "page_size" version = "0.6.0" @@ -1730,6 +1835,12 @@ version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" +[[package]] +name = "pathdiff" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8835116a5c179084a830efb3adc117ab007512b535bc1a21c991d3b32a6b44dd" + [[package]] name = "peeking_take_while" version = "0.1.2" @@ -1742,6 +1853,51 @@ version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" +[[package]] +name = "pest" +version = "2.7.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdbef9d1d47087a895abd220ed25eb4ad973a5e26f6a4367b038c25e28dfc2d9" +dependencies = [ + "memchr", + "thiserror", + "ucd-trie", +] + +[[package]] +name = "pest_derive" +version = "2.7.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d3a6e3394ec80feb3b6393c725571754c6188490265c61aaf260810d6b95aa0" +dependencies = [ + "pest", + "pest_generator", +] + +[[package]] +name = "pest_generator" +version = "2.7.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94429506bde1ca69d1b5601962c73f4172ab4726571a59ea95931218cb0e930e" +dependencies = [ + "pest", + "pest_meta", + "proc-macro2", + "quote", + "syn 2.0.76", +] + +[[package]] +name = "pest_meta" +version = "2.7.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac8a071862e93690b6e34e9a5fb8e33ff3734473ac0245b27232222c4906a33f" +dependencies = [ + "once_cell", + "pest", + "sha2", +] + [[package]] name = "petgraph" version = "0.6.5" @@ -1974,17 +2130,28 @@ checksum = "106dd99e98437432fed6519dedecfade6a06a73bb7b2a1e019fdd2bee5778d94" [[package]] name = "protobuf-build" -version = "0.14.1" +version = "0.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2df9942df2981178a930a72d442de47e2f0df18ad68e50a30f816f1848215ad0" +checksum = "c852d9625b912c3e50480cdc701f60f49890b5d7ad46198dd583600f15e7c6ec" dependencies = [ "bitflags 1.3.2", "proc-macro2", "prost-build", + "protobuf-src", "quote", + "regex", "syn 1.0.109", ] +[[package]] +name = "protobuf-src" +version = "1.1.0+21.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7ac8852baeb3cc6fb83b93646fb93c0ffe5d14bf138c945ceb4b9948ee0e3c1" +dependencies = [ + "autotools", +] + [[package]] name = "quote" version = "1.0.37" @@ -1997,8 +2164,6 @@ dependencies = [ [[package]] name = "raft-proto" version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb6884896294f553e8d5cfbdb55080b9f5f2f43394afff59c9f077e0f4b46d6b" dependencies = [ "lazy_static", "prost", @@ -2015,6 +2180,7 @@ dependencies = [ "built", "bytes", "chrono", + "config", "heed", "heed-traits", "jopemachine-raft", @@ -2160,6 +2326,28 @@ dependencies = [ "librocksdb-sys", ] +[[package]] +name = "ron" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b91f7eff05f748767f183df4320a63d6936e9c6107d97c9e6bdd9784f4289c94" +dependencies = [ + "base64 0.21.7", + "bitflags 2.6.0", + "serde", + "serde_derive", +] + +[[package]] +name = "rust-ini" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e2a3bcec1f113553ef1c88aae6c020a369d03d55b58de9869a0908930385091" +dependencies = [ + "cfg-if", + "ordered-multimap", +] + [[package]] name = "rustc-demangle" version = "0.1.24" @@ -2326,6 +2514,17 @@ dependencies = [ "digest", ] +[[package]] +name = "sha2" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + [[package]] name = "shlex" version = "1.3.0" @@ -2628,6 +2827,15 @@ dependencies = [ "time-core", ] +[[package]] +name = "tiny-keccak" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" +dependencies = [ + "crunchy", +] + [[package]] name = "tinyvec" version = "1.8.0" @@ -2879,6 +3087,12 @@ version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" +[[package]] +name = "ucd-trie" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971" + [[package]] name = "unicode-bidi" version = "0.3.15" @@ -3172,6 +3386,15 @@ dependencies = [ "memchr", ] +[[package]] +name = "yaml-rust" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85" +dependencies = [ + "linked-hash-map", +] + [[package]] name = "zerocopy" version = "0.7.35" @@ -3220,7 +3443,3 @@ dependencies = [ "cc", "pkg-config", ] - -[[patch.unused]] -name = "jopemachine-raft" -version = "0.7.9" diff --git a/examples/memstore/common_raftnode_config.toml b/examples/memstore/common_raftnode_config.toml new file mode 100644 index 00000000..e87028d0 --- /dev/null +++ b/examples/memstore/common_raftnode_config.toml @@ -0,0 +1,9 @@ +log_dir = "./logs" +compacted_log_size_threshold = 1073741824 +save_compacted_logs = true + +[raft_config] +tick_interval = 0.2 +election_tick = 10 +heartbeat_tick = 3 +omit_heartbeat_log = false diff --git a/examples/src/config.rs b/examples/src/config.rs index 4f2732d1..50425d61 100644 --- a/examples/src/config.rs +++ b/examples/src/config.rs @@ -1,4 +1,6 @@ -use raftify::{Config, ConfigBuilder, Peers, RaftConfig}; +use std::path::Path; + +use raftify::{load_configs, Config, ConfigBuilder, Peers}; #[cfg(feature = "tls")] use raftify::TlsConfig; @@ -6,26 +8,22 @@ use raftify::TlsConfig; use crate::utils::{ensure_directory_exist, get_storage_path}; pub fn build_config(node_id: u64, initial_peers: Option) -> Config { - let raft_config = RaftConfig { - id: node_id, - election_tick: 10, - heartbeat_tick: 3, - omit_heartbeat_log: false, - ..Default::default() - }; - let storage_pth = get_storage_path("./logs", node_id); ensure_directory_exist(&storage_pth).expect("Failed to create storage directory"); - #[allow(unused_mut)] - let mut config_builder = ConfigBuilder::new() - .log_dir("./logs".to_owned()) - .save_compacted_logs(true) - .compacted_log_dir("./logs".to_owned()) - .compacted_log_size_threshold(1024 * 1024 * 1024) - .raft_config(raft_config) - .tick_interval(0.2); + let path = Path::new(file!()) + .parent() + .unwrap() + .parent() + .unwrap() + .join("memstore/common_raftnode_config.toml"); + + let config_builder = ConfigBuilder::from_config( + load_configs(path.to_str().unwrap()).expect("Failed to load common config"), + ) + .set_node_id(node_id); + #[allow(unused_mut)] #[cfg(feature = "tls")] { let client_tls_config = TlsConfig { diff --git a/harness/src/config.rs b/harness/src/config.rs index 52d11f38..6b5364b2 100644 --- a/harness/src/config.rs +++ b/harness/src/config.rs @@ -1,6 +1,5 @@ -use raftify::{Config, ConfigBuilder, Peers, RaftConfig}; - use crate::utils::{ensure_directory_exist, get_storage_path}; +use raftify::{Config, ConfigBuilder, Peers, RaftConfig}; pub fn build_config(node_id: u64, initial_peers: Option) -> Config { let raft_config = RaftConfig { diff --git a/raft-rs b/raft-rs index 48bb3f82..5bee0aa2 160000 --- a/raft-rs +++ b/raft-rs @@ -1 +1 @@ -Subproject commit 48bb3f82cbc52c46464bf3ff3702df7d41243946 +Subproject commit 5bee0aa292602180758bf01549c2a7c01f841ccb diff --git a/raftify-cli/src/commands/debug.rs b/raftify-cli/src/commands/debug.rs index 0c04bc61..e77d0014 100644 --- a/raftify-cli/src/commands/debug.rs +++ b/raftify-cli/src/commands/debug.rs @@ -3,17 +3,18 @@ use serde_json::Value; use std::{collections::HashMap, fs, path::Path, sync::Arc}; use raftify::{ - create_client, raft::{ + create_client, + raft::{ formatter::{format_entry, format_snapshot}, logger::Slogger, Storage, - }, raft_node::utils::format_debugging_info, raft_service, Config, ConfigBuilder, HeedStorage, Result, StableStorage, StorageType + }, + raft_node::utils::format_debugging_info, + raft_service, ConfigBuilder, HeedStorage, Result, StableStorage, StorageType, }; pub fn debug_persisted(path: &str, logger: slog::Logger) -> Result<()> { - let config = ConfigBuilder::new() - .log_dir(path.to_string()) - .build(); + let config = ConfigBuilder::new().log_dir(path.to_string()).build(); let storage = match LogStorage::STORAGE_TYPE { StorageType::Heed => HeedStorage::create( @@ -55,7 +56,10 @@ pub fn debug_persisted(path: &str, logger: slog::Logg Ok(()) } -pub fn debug_persisted_all(path_str: &str, logger: slog::Logger) -> Result<()> { +pub fn debug_persisted_all( + path_str: &str, + logger: slog::Logger, +) -> Result<()> { let path = match fs::canonicalize(Path::new(&path_str)) { Ok(absolute_path) => absolute_path, Err(e) => { diff --git a/raftify/Cargo.toml b/raftify/Cargo.toml index 43dc25b5..9f47d044 100644 --- a/raftify/Cargo.toml +++ b/raftify/Cargo.toml @@ -16,7 +16,7 @@ bytes = "1.7.2" log = { version = "0.4", features = ["std"] } parking_lot = "0.12.3" prost = "0.11" -raft = { version = "0.7.10", features = ["prost-codec", "default-logger"], default-features = false, package = "jopemachine-raft" } +raft = { version = "0.7.13", features = ["prost-codec", "default-logger"], default-features = false, package = "jopemachine-raft" } serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" slog = "2" @@ -29,6 +29,7 @@ chrono = "0.4.38" heed = { version = "0.20.5", optional = true } heed-traits = { version = "0.20", optional = true } rocksdb = { version = "0.19.0", optional = true } +config = "0.14.0" [features] default = ["heed_storage", "tls"] diff --git a/raftify/config.toml b/raftify/config.toml new file mode 100644 index 00000000..9e3b2c4f --- /dev/null +++ b/raftify/config.toml @@ -0,0 +1,36 @@ +[node] +id = 1 + +[raft] +election_tick = 10 +heartbeat_tick = 3 +omit_heartbeat_log = true +applied = 0 +max_size_per_msg = 1048576 +max_inflight_msgs = 256 +check_quorum = true +pre_vote = false +min_election_tick = 5 +max_election_tick = 15 +read_only_option = "Safe" +skip_bcast_commit = false +batch_append = true +priority = 0 +max_uncommitted_size = 100 +max_committed_size_per_ready = 256 + +[storage] +log_dir = "./logs" +compacted_log_size_threshold = 1073741824 + +[cluster] +cluster_id = "default" +tick_interval = 0.1 +lmdb_map_size = 1073741824 +conf_change_request_timeout = 2.0 + +[tls] +cert_path = "/path/cert" +key_path = "/path/key" +ca_cert_path = "/path/ca" +domain_name = "example.com" diff --git a/raftify/src/config.rs b/raftify/src/config.rs deleted file mode 100644 index 3e5142e6..00000000 --- a/raftify/src/config.rs +++ /dev/null @@ -1,253 +0,0 @@ -use std::fmt; - -use serde::{Deserialize, Serialize}; - -use crate::{error::Error, raft::Config as RaftConfig, InitialRole, Peers, Result}; - -// NOTE: This TLS Config is a type commonly used in both RaftServer and RaftClient. -#[derive(Clone, Debug, Deserialize, Serialize)] -pub struct TlsConfig { - pub cert_path: Option, - pub key_path: Option, - pub ca_cert_path: Option, - pub domain_name: Option, -} - -#[derive(Clone)] -pub struct Config { - pub(crate) raft_config: RaftConfig, - pub(crate) log_dir: String, - pub(crate) save_compacted_logs: bool, - pub(crate) compacted_log_dir: String, - pub(crate) compacted_log_size_threshold: u64, - pub(crate) tick_interval: f32, - pub(crate) lmdb_map_size: u64, - pub(crate) bootstrap_from_snapshot: bool, - pub(crate) cluster_id: String, - pub(crate) conf_change_request_timeout: f32, - pub(crate) initial_peers: Option, - pub(crate) snapshot_interval: Option, - pub(crate) client_tls_config: Option, - pub(crate) server_tls_config: Option, -} - -pub struct ConfigBuilder { - config: Config, - global_client_tls_config: Option, -} - -impl ConfigBuilder { - pub fn new() -> Self { - Self { - config: Config::default(), - global_client_tls_config: None, - } - } - - pub fn log_dir(mut self, log_dir: String) -> Self { - self.config.log_dir = log_dir; - self - } - - pub fn save_compacted_logs(mut self, save: bool) -> Self { - self.config.save_compacted_logs = save; - self - } - - pub fn compacted_log_dir(mut self, dir: String) -> Self { - self.config.compacted_log_dir = dir; - self - } - - pub fn compacted_log_size_threshold(mut self, threshold: u64) -> Self { - self.config.compacted_log_size_threshold = threshold; - self - } - - pub fn raft_config(mut self, raft_config: RaftConfig) -> Self { - self.config.raft_config = raft_config; - self - } - - pub fn tick_interval(mut self, interval: f32) -> Self { - self.config.tick_interval = interval; - self - } - - pub fn lmdb_map_size(mut self, size: u64) -> Self { - self.config.lmdb_map_size = size; - self - } - - pub fn cluster_id(mut self, cluster_id: String) -> Self { - self.config.cluster_id = cluster_id; - self - } - - pub fn conf_change_request_timeout(mut self, timeout: f32) -> Self { - self.config.conf_change_request_timeout = timeout; - self - } - - pub fn initial_peers(mut self, peers: Peers) -> Self { - self.config.initial_peers = Some(peers); - self - } - - pub fn snapshot_interval(mut self, interval: f32) -> Self { - self.config.snapshot_interval = Some(interval); - self - } - - pub fn server_tls_config(mut self, config: TlsConfig) -> Self { - self.config.server_tls_config = Some(config); - self - } - - pub fn bootstrap_from_snapshot(mut self, v: bool) -> Self { - self.config.bootstrap_from_snapshot = v; - self - } - - pub fn global_client_tls_config(mut self, config: TlsConfig) -> Self { - self.global_client_tls_config = Some(config); - self - } - - pub fn build(mut self) -> Config { - self.config.client_tls_config = self.global_client_tls_config.clone(); - - if let Some(mut initial_peers) = self.config.initial_peers.clone() { - if let Some(self_peer) = initial_peers.get_mut(&self.config.raft_config.id) { - if self_peer.client_tls_config.is_none() { - self_peer.client_tls_config = self.global_client_tls_config.clone(); - } else { - self.config.client_tls_config = self_peer.client_tls_config.clone(); - } - } - self.config.initial_peers = Some(initial_peers); - } - - self.config - } -} - -impl Config { - pub fn validate(&self) -> Result<()> { - if self.initial_peers.is_some() { - let leaders = self - .initial_peers - .clone() - .unwrap() - .inner - .into_iter() - .filter(|(_, peer)| peer.initial_role == InitialRole::Leader) - .map(|(key, _)| key) - .collect::>(); - - if leaders.len() > 1 { - return Err(Error::ConfigInvalid( - "initial_peers should contain at most 1 leaders".to_owned(), - )); - } - } - - self.raft_config.validate()?; - Ok(()) - } - - pub fn get_log_dir(&self) -> &str { - &self.log_dir - } -} - -impl Default for Config { - fn default() -> Self { - Self { - raft_config: RaftConfig::default(), - log_dir: String::from("./"), - save_compacted_logs: false, - compacted_log_dir: String::from("./"), - compacted_log_size_threshold: 1024 * 1024 * 1024, - tick_interval: 0.1, - lmdb_map_size: 1024 * 1024 * 1024, - cluster_id: String::from("default"), - conf_change_request_timeout: 2.0, - initial_peers: None, - snapshot_interval: None, - bootstrap_from_snapshot: false, - client_tls_config: None, - server_tls_config: None, - } - } -} - -impl fmt::Debug for Config { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!( - f, - "Config {{ \ - raft_config: {{ \ - id: {id}, \ - election_tick: {election_tick}, \ - heartbeat_tick: {heartbeat_tick}, \ - applied: {applied}, \ - max_size_per_msg: {max_size_per_msg}, \ - max_inflight_msgs: {max_inflight_msgs}, \ - check_quorum: {check_quorum}, \ - pre_vote: {pre_vote}, \ - min_election_tick: {min_election_tick}, \ - max_election_tick: {max_election_tick}, \ - read_only_option: {read_only_option:?}, \ - skip_bcast_commit: {skip_bcast_commit}, \ - batch_append: {batch_append}, \ - priority: {priority}, \ - max_uncommitted_size: {max_uncommitted_size}, \ - max_committed_size_per_ready: {max_committed_size_per_ready}, \ - }}, \ - log_dir: {log_dir}, \ - bootstrap_from_snapshot: {bootstrap_from_snapshot}, \ - save_compacted_logs: {save_compacted_logs}, \ - compacted_log_dir: {compacted_log_dir}, \ - compacted_log_size_threshold: {compacted_log_size_threshold}, \ - snapshot_interval: {snapshot_interval:?}, \ - tick_interval: {tick_interval}, \ - initial_peers: {initial_peers:?}, \ - lmdb_map_size: {lmdb_map_size}, \ - cluster_id: {cluster_id}, \ - conf_change_request_timeout: {conf_change_request_timeout}, \ - client_tls_config: {client_tls_config:?}, \ - server_tls_config: {server_tls_config:?}, \ - }}", - id = self.raft_config.id, - election_tick = self.raft_config.election_tick, - heartbeat_tick = self.raft_config.heartbeat_tick, - applied = self.raft_config.applied, - max_size_per_msg = self.raft_config.max_size_per_msg, - max_inflight_msgs = self.raft_config.max_inflight_msgs, - check_quorum = self.raft_config.check_quorum, - pre_vote = self.raft_config.pre_vote, - min_election_tick = self.raft_config.min_election_tick, - max_election_tick = self.raft_config.max_election_tick, - read_only_option = self.raft_config.read_only_option, - skip_bcast_commit = self.raft_config.skip_bcast_commit, - batch_append = self.raft_config.batch_append, - priority = self.raft_config.priority, - max_uncommitted_size = self.raft_config.max_uncommitted_size, - max_committed_size_per_ready = self.raft_config.max_committed_size_per_ready, - log_dir = self.log_dir, - save_compacted_logs = self.save_compacted_logs, - compacted_log_dir = self.compacted_log_dir, - compacted_log_size_threshold = self.compacted_log_size_threshold, - snapshot_interval = self.snapshot_interval, - tick_interval = self.tick_interval, - lmdb_map_size = self.lmdb_map_size, - initial_peers = self.initial_peers, - cluster_id = self.cluster_id, - conf_change_request_timeout = self.conf_change_request_timeout, - bootstrap_from_snapshot = self.bootstrap_from_snapshot, - client_tls_config = self.client_tls_config, - server_tls_config = self.server_tls_config, - ) - } -} diff --git a/raftify/src/config/config_builder.rs b/raftify/src/config/config_builder.rs new file mode 100644 index 00000000..ec9536d4 --- /dev/null +++ b/raftify/src/config/config_builder.rs @@ -0,0 +1,120 @@ +use raft::Config as RaftConfig; +use serde::{Deserialize, Serialize}; + +use crate::Peers; + +use super::{Config, TlsConfig}; + +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct ConfigBuilder { + pub(crate) config: Config, + global_client_tls_config: Option, +} + +impl ConfigBuilder { + pub fn new() -> Self { + Self { + config: Config::default(), + global_client_tls_config: None, + } + } + + pub fn from_config(config: Config) -> Self { + Self { + config, + global_client_tls_config: None, + } + } + + pub fn log_dir(mut self, log_dir: String) -> Self { + self.config.log_dir = log_dir; + self + } + + pub fn save_compacted_logs(mut self, save: bool) -> Self { + self.config.save_compacted_logs = save; + self + } + + pub fn compacted_log_dir(mut self, dir: String) -> Self { + self.config.compacted_log_dir = dir; + self + } + + pub fn compacted_log_size_threshold(mut self, threshold: u64) -> Self { + self.config.compacted_log_size_threshold = threshold; + self + } + + pub fn raft_config(mut self, raft_config: RaftConfig) -> Self { + self.config.raft_config = raft_config; + self + } + + pub fn set_node_id(mut self, node_id: u64) -> Self { + self.config.raft_config.id = node_id; + self + } + + pub fn tick_interval(mut self, interval: f32) -> Self { + self.config.tick_interval = interval; + self + } + + pub fn lmdb_map_size(mut self, size: u64) -> Self { + self.config.lmdb_map_size = size; + self + } + + pub fn cluster_id(mut self, cluster_id: String) -> Self { + self.config.cluster_id = cluster_id; + self + } + + pub fn conf_change_request_timeout(mut self, timeout: f32) -> Self { + self.config.conf_change_request_timeout = timeout; + self + } + + pub fn initial_peers(mut self, peers: Peers) -> Self { + self.config.initial_peers = Some(peers); + self + } + + pub fn snapshot_interval(mut self, interval: f32) -> Self { + self.config.snapshot_interval = Some(interval); + self + } + + pub fn server_tls_config(mut self, config: TlsConfig) -> Self { + self.config.server_tls_config = Some(config); + self + } + + pub fn bootstrap_from_snapshot(mut self, v: bool) -> Self { + self.config.bootstrap_from_snapshot = v; + self + } + + pub fn global_client_tls_config(mut self, config: TlsConfig) -> Self { + self.global_client_tls_config = Some(config); + self + } + + pub fn build(mut self) -> Config { + self.config.client_tls_config = self.global_client_tls_config.clone(); + + if let Some(mut initial_peers) = self.config.initial_peers.clone() { + if let Some(self_peer) = initial_peers.get_mut(&self.config.raft_config.id) { + if self_peer.client_tls_config.is_none() { + self_peer.client_tls_config = self.global_client_tls_config.clone(); + } else { + self.config.client_tls_config = self_peer.client_tls_config.clone(); + } + } + self.config.initial_peers = Some(initial_peers); + } + + self.config + } +} diff --git a/raftify/src/config/mod.rs b/raftify/src/config/mod.rs new file mode 100644 index 00000000..242d9869 --- /dev/null +++ b/raftify/src/config/mod.rs @@ -0,0 +1,92 @@ +use config::{Config as BaseConfig, ConfigError, File}; +use raft::Config as RaftConfig; +use serde::{Deserialize, Serialize}; + +use crate::{error::Error, error::Result, peers::Peers, InitialRole}; +pub mod config_builder; + +// NOTE: TlsConfig is used in both RaftServer and RaftClient. +#[derive(Clone, Debug, Default, Deserialize, Serialize)] +#[serde(default)] +pub struct TlsConfig { + pub cert_path: Option, + pub key_path: Option, + pub ca_cert_path: Option, + pub domain_name: Option, +} + +#[derive(Clone, Debug, Deserialize, Serialize)] +#[serde(default)] +pub struct Config { + pub(crate) raft_config: RaftConfig, + pub(crate) log_dir: String, + pub(crate) save_compacted_logs: bool, + pub(crate) compacted_log_dir: String, + pub(crate) compacted_log_size_threshold: u64, + pub(crate) tick_interval: f32, + pub(crate) lmdb_map_size: u64, + pub(crate) bootstrap_from_snapshot: bool, + pub(crate) cluster_id: String, + pub(crate) conf_change_request_timeout: f32, + pub(crate) initial_peers: Option, + pub(crate) snapshot_interval: Option, + pub(crate) client_tls_config: Option, + pub(crate) server_tls_config: Option, +} + +impl Default for Config { + fn default() -> Self { + Self { + raft_config: RaftConfig::default(), + log_dir: String::from("./"), + save_compacted_logs: false, + compacted_log_dir: String::from("./"), + compacted_log_size_threshold: 1024 * 1024 * 1024, + tick_interval: 0.1, + lmdb_map_size: 1024 * 1024 * 1024, + cluster_id: String::from("default"), + conf_change_request_timeout: 2.0, + initial_peers: None, + snapshot_interval: None, + bootstrap_from_snapshot: false, + client_tls_config: None, + server_tls_config: None, + } + } +} + +impl Config { + pub fn validate(&self) -> Result<()> { + if self.initial_peers.is_some() { + let leaders = self + .initial_peers + .clone() + .unwrap() + .inner + .into_iter() + .filter(|(_, peer)| peer.initial_role == InitialRole::Leader) + .map(|(key, _)| key) + .collect::>(); + + if leaders.len() > 1 { + return Err(Error::ConfigInvalid( + "initial_peers should contain at most 1 leaders".to_owned(), + )); + } + } + + self.raft_config.validate()?; + Ok(()) + } + + pub fn get_log_dir(&self) -> &str { + &self.log_dir + } +} + +pub fn load_configs(filename: &str) -> std::result::Result { + let file_config_builder = BaseConfig::builder().add_source(File::with_name(filename)); + let file_config = file_config_builder.build()?; + + file_config.try_deserialize() +} diff --git a/raftify/src/error.rs b/raftify/src/error.rs index ef535393..fb3231e9 100644 --- a/raftify/src/error.rs +++ b/raftify/src/error.rs @@ -1,3 +1,4 @@ +use config::ConfigError; use thiserror::Error as ThisError; pub type Result = std::result::Result; @@ -41,6 +42,9 @@ pub enum Error { EncodingError(String), #[error("Decoding error")] DecodingError(String), + + #[error("Config file format invalid")] + ConfigFileParsingError(#[from] ConfigError), } #[derive(Debug, ThisError)] diff --git a/raftify/src/lib.rs b/raftify/src/lib.rs index e64edf80..f109eafc 100644 --- a/raftify/src/lib.rs +++ b/raftify/src/lib.rs @@ -25,7 +25,7 @@ pub use { pub use crate::{ cluster_join_ticket::ClusterJoinTicket, - config::{Config, ConfigBuilder, TlsConfig}, + config::{config_builder::ConfigBuilder, load_configs, Config, TlsConfig}, error::{Error, Result}, log_entry::AbstractLogEntry, peer::Peer, diff --git a/raftify/src/storage/heed_storage/mod.rs b/raftify/src/storage/heed_storage/mod.rs index 3b66dbbb..7cdfd405 100644 --- a/raftify/src/storage/heed_storage/mod.rs +++ b/raftify/src/storage/heed_storage/mod.rs @@ -547,13 +547,14 @@ mod test { use std::panic::{self, AssertUnwindSafe}; use std::sync::Arc; + use crate::config::config_builder::ConfigBuilder; use crate::raft::{ default_logger, eraftpb::{Entry, Snapshot}, logger::Slogger, Config as RaftConfig, Error as RaftError, GetEntriesContext, Storage, StorageError, }; - use crate::{Config, ConfigBuilder, HeedStorage, StableStorage}; + use crate::{Config, HeedStorage, StableStorage}; use prost::Message; fn new_entry(index: u64, term: u64) -> Entry {